【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

【第一题】数组中数字出现的次数II

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)
分析:
方法一:集合;
方法二:哈希表;

//哈希好慢
class Solution {
    public int singleNumber(int[] nums) {
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = 0;i < nums.length;i++){
            map.put(nums[i],map.getOrDefault(nums[i],0)+1);
        }
        for(int key:map.keySet()){
            if(map.get(key) == 1){return key;}
        }
        return -1;
    }
}

【第二题】最小的K个数

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

分析:
方法一:排序;输出前k个元素。
方法二:大根堆;用前k个元素组成一个大根堆,然后每进来一个元素就和堆根进行比较,如果大则抛之,如果小则加入堆,把堆根元素(堆中最大值)弹出堆。
方法三:分治;将所有元素分为k部分,每部分选出一个最小的。
方法四:二叉搜索树。
方法五:先找到第k小的数,然后遍历一遍数组,将所有比k小的数加入结果集。
方法六:快速选择。利用快排的partition思想,进行不完全的快速排序,只需要直到最小的k个数是哪些即可。

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

//大根堆
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
		int[] vec = new int[k];
		if(k == 0){return vec;}
		PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>(){
			public int compare(Integer num1,Integer num2){
				return num2 - num1;
			}
		});
		for(int i = 0;i < k;++i){
			queue.offer(arr[i]);
		}
		for(int i = k;i < arr.length;i++){
			if(queue.peek() > arr[i]){
				queue.poll();
				queue.offer(arr[i]);
			}
		}
		for(int i = 0;i < k;++i){
			vec[i] = queue.poll();
		}
		return vec;
	}
}
public int[] getLeastNumbers(int[] arr, int k) {
    if (k == 0) {
        return new int[0];
    } else if (arr.length <= k) {
        return arr;
    }
    
    // 原地不断划分数组
    partitionArray(arr, 0, arr.length - 1, k);
    
    // 数组的前 k 个数此时就是最小的 k 个数,将其存入结果
    int[] res = new int[k];
    for (int i = 0; i < k; i++) {
        res[i] = arr[i];
    }
    return res;
}

void partitionArray(int[] arr, int lo, int hi, int k) {
    // 做一次 partition 操作
    int m = partition(arr, lo, hi);
    // 此时数组前 m 个数,就是最小的 m 个数
    if (k == m) {
        // 正好找到最小的 k(m) 个数
        return;
    } else if (k < m) {
        // 最小的 k 个数一定在前 m 个数中,递归划分
        partitionArray(arr, lo, m-1, k);
    } else {
        // 在右侧数组中寻找最小的 k-m 个数
        partitionArray(arr, m+1, hi, k);
    }
}

// partition 函数和快速排序中相同,具体可参考快速排序相关的资料
// 代码参考 Sedgewick 的《算法4》
int partition(int[] a, int lo, int hi) {
    int i = lo;
    int j = hi + 1;
    int v = a[lo];
    while (true) { 
        while (a[++i] < v) {
            if (i == hi) {
                break;
            }
        }
        while (a[--j] > v) {
            if (j == lo) {
                break;
            }
        }

        if (i >= j) {
            break;
        }
        swap(a, i, j);
    }
    swap(a, lo, j);

    // a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
    return j;
}

void swap(int[] a, int i, int j) {
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}


作者:nettee
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/tu-jie-top-k-wen-ti-de-liang-chong-jie-fa-you-lie-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

【第三题】扑克牌中的顺子

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

分析:首先,有重复的牌就不能作为顺子;第二,一个0只能填一个“坑”(根据0的个数、下标与元素之差,两者之间的关系来判断坑)

//执行用时:1 ms, 在所有 Java 提交中击败了91.86% 的用户
//内存消耗:36.1 MB, 在所有 Java 提交中击败了10.17% 的用户
class Solution {
    public boolean isStraight(int[] nums) {
        if(nums.length <= 1){return true;}
        Arrays.sort(nums);
        //gap为下标与数组之差
        //count为0的个数
        int gap = 0,i = 0,count = 0;
        while(nums[i]==0){
            i++;
            count++;
        }
        gap = nums[i] - i;
        //System.out.println(count + " " + i +" "+gap);
        int j = 0;
        for(j = i;j < nums.length-1;j++){
            //判断重复元素
            if(nums[j] == nums[j+1]){return false;}
            if(nums[j] - j - gap <= count){
                continue; 
            }else{
                return false;
            }
        }
        //判断最后一个位置是否满足条件
        return nums[j]-j-gap <= count?true:false;
    }
}

【第五题】数组中数字出现的次数

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

分析:空间复杂度为O(1)——立即推 位运算!
参考链接:接地气讲解(分组位运算)「还不懂就来P城砍我」

//执行用时:8 ms, 在所有 Java 提交中击败了18.05% 的用户
//内存消耗:40.1 MB, 在所有 Java 提交中击败了63.30% 的用户
//双指针
class Solution {
    public int[] singleNumbers(int[] nums) {
        if(nums.length == 2){
            if(nums[0]!=nums[1]){
                return nums;
            }
        }
        Arrays.sort(nums);
        int[] arr = new int[2];
        //双指针比较
        int i = 0,j = 1;
        int k = 0;
        while(i < nums.length && j < nums.length){
            if(nums[i] ==nums[j]){
                i+=2;
                j+=2;
            }else{
                if(k <=1){
                    arr[k++] = nums[i];
                }else{break;}
                i++;
                j++;
            }
        }
        if(k <= 1){
            arr[k] = nums[i];
        }
        return arr;
    }
}
class Solution {
    public int[] singleNumbers(int[] nums) {
        int sum = 0;
        //最后得到的是这两个不同的数的异或结果
        for(int num:nums){
            sum ^= num;
        }
        int flag = sum & (-sum);

        int res = 0;
        //分成两个组进行异或
        for(int val:nums){
            if((flag & val) !=0){
                res ^= val;
            }
        }
        return new int[]{res,sum ^ res};
    }
}
public int[] singleNumbers(int[] nums) {
        //xor用来计算nums的异或和
        int xor = 0;

        // 计算异或和 并存到xor
        // e.g. [2,4,2,3,3,6] 异或和:(2^2)^(3^3)^(4^6)=2=010
        for(int num : nums) xor ^= num;

        //设置mask为1,则二进制为0001
        // mask是一个二进制数,且其中只有一位是1,其他位全是0,比如000010,
        // 表示我们用倒数第二位作为分组标准,倒数第二位是0的数字分到一组,倒数第二位是1的分到另一组
        int mask = 1;

        // & operator只有1&1时等于1 其余等于0
        // 用上面的e.g. 4和6的二进制是不同的 我们从右到左找到第一个不同的位就可以分组 4=0100 6=0110
        // 根据e.g. 010 & 001 = 000 = 0则 mask=010
        // 010 & 010 != 0 所以mask=010
        // 之后就可以用mask来将数组里的两个数分区分开
        while((xor & mask)==0){
            mask <<= 1;
        }

        //两个只出现一次的数字
        int a=0, b=0;

        for(int num : nums){
            //根据&是否为0区分将两个数字分区,并分别求异或和
            if((num & mask)==0) a ^= num;
            else{
                b ^= num;
            }
        }
        return new int[]{a,b};
    }

【第六题】从上到下打印二叉树

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

分析:直接利用队列层序遍历。

class Solution {
    public int[] levelOrder(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null){return list.stream().mapToInt(Integer::intValue).toArray();}
        //if(root == null){return list.toArray(new Integer[0]);}
        Queue<TreeNode> q = new LinkedList<>();
        q.add(root);
        while(!q.isEmpty()){
            int count = q.size();
            for(int i = 0;i < count;i++){
                TreeNode node = q.poll();
                //arr[j++] = node.val;
                list.add(node.val);
                if(node.left!=null){
                    q.add(node.left);
                }
                if(node.right !=null){
                    q.add(node.right);
                }
            }
        }
        //将Integer类型list复制为int数组 使用了java8的stram新特性
        return list.stream().mapToInt(Integer::intValue).toArray();
        //return list.toArray(new Integer[list.size()]);
    }
}

【第七题】重建二叉树

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

分析:根据前序中序遍历还原二叉树。
1、根据前序遍历找到父节点;
2、根据父节点在中序遍历中确定左右子树的顺序;

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

class Solution{
	public TreeNode buildTree(int[] preorder,int[] inorder){
		int n = preorder.length;
		if(n == 0){return null;}
		int rootVal = preorder[0];
		
		int index = 0;
		for(int i = 0;i < n;i++){
			if(inorder[i] == rootVal){
				index = i;
				break;
			}
		}
		TreeNode node = new TreeNode(rootVal);
		root.left = buildTree(Arrays.copyRange(preorder,1,index+1),Arrays.copyRange(inorder,0,Index));
		root.right = buildTree(Arrays.copyRange(preorder,index+1,n),Arrays.copyRange(inorder,index+1,n));
		return root;
	}
}

【第八题】丑数(动态规划挖坑)

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

【第九题】二叉树中和为某一值的路径

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

分析:深度优先.

class Solution{
	LinkedList<List<Integer>> res = new LinkedList<>();
	LinkedList<Integer> path = new LinkedList<>();
	public List<List<Integer>> pathSum(TreeNode root,int sum){
		recur(root,sum);
		return res;
	}
	void recur(TreeNode root,int tar){
		if(root == null) return;
		path.add(root.val);
		tar -= root.val;
		if(tar == 0 && root.left == null && root.right == null){
			res.add(new LinkedList(path));
		}
		recur(root.left,tar);
		recur(root.right,tar);
		path.removeLast();
	}
}
class Solution {
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        if(root==null){
            return res;
        }
        dfs(root,sum);
        return res;

    }
    public void dfs(TreeNode root,int sum){
        sum-=root.val;
        if(sum==0&&root.left==null&&root.right==null){
            temp.add(root.val);
            res.add(new ArrayList<>(temp));
            temp.remove(temp.size()-1);
            return;
        }
        temp.add(root.val);
        if(root.left!=null){
            dfs(root.left,sum);
        }
        if(root.right!=null){
            dfs(root.right,sum);
        }
        temp.remove(temp.size()-1);
       
    }
}

19216801
【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

【第十题】汉诺塔问题

分析:假如是一步,则直接从A移动到C;假如是两个以上的盘子,则先(A,B),再(A,C),再(B,C)。

class Solution{
	public void hanota(List<Integer> A,List<Integer> B,List<Integer> C){
		int n = A.size(); 
		move(n,A,B,C);
	}
	public void move(int n,List<Integer> A,List<Integer> B,List<Integer> C){
		if(n == 1){
			C.add(A.size()-1);
			A.remove(A.size()-1);
		}
		move(n-1,A,C,B);
		C.add(A.size()-1);
		A.remove(A.size()-1);
		move(n-1,B,A,C);
	}
}

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

【第十一题】字符串压缩

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)
分析:
方法一:一个for循环

class Solution {
    public String compressString(String S) {
        if(S.length() <= 2){return S;}
        StringBuilder sb = new StringBuilder();
        
        int count = 1;
        int i = 0;
        for(i = 0;i < S.length()-1;i++){
            if(S.charAt(i) == S.charAt(i+1)){
                count++;
            }else{
                sb.append(S.charAt(i));
                sb.append(count+"");
                count = 1;
            }
        }
        sb.append(S.charAt(i));
        sb.append(count);
        String S1 = sb.toString();
        return S1.length() >= S.length()?S:S1;
    }
}

方法二:快慢指针

class Solution {
    // 快慢指针
    public String compressString(String S) {
        if(S.length() == 0) return S;
        char[] chars = S.toCharArray();
        StringBuilder sb = new StringBuilder();
        int slow = 0, fast = 0;
        while(fast <= chars.length){
            if(fast == chars.length){
                sb.append(chars[slow]).append(fast-slow);
            }else if(chars[fast] != chars[slow]){
                sb.append(chars[slow]).append(fast-slow);
                slow = fast;
            }
            fast++;
        }
        return sb.length() < chars.length ? sb.toString() : S;
    }
}

【第十二题】交换数字

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

分析:

class Solution {
    public int[] swapNumbers(int[] numbers) {
        numbers[0] = numbers[0] ^ numbers[1];
        numbers[1] = numbers[0] ^ numbers[1];
        numbers[0] = numbers[0] ^ numbers[1];
        return numbers;
    }
}
//但是有溢出风险
class Solution{
	public int[] swapNumbers(int[] numbers){
		numbers[0] = numbers[0] + numbers[1];
		numbers[1] = numbers[0] - numbers[1];
		numbers[0] = numbers[0] - numbers[1];
		return numbers;
	}
}

【第十三题】幂集

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

分析:涉及到穷举和剪枝——立即推,回溯。

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<Integer> list = new LinkedList<>();
        List<List<Integer>> res = new ArrayList<>();
        backTrace(nums,0,list,res);
        return res;
    }
    private void backTrace(int[] nums,int cur,List<Integer> list,List<List<Integer>> res){
        /**回溯模板:
            1、递归出口
            2、for循环
                2.1 将元素加入列表
                2.2 向下递归
                2.3 向上回溯
        **/
        res.add(new ArrayList<>(list));
        for(int i = cur;i < nums.length;i++){
            list.add(nums[i]);
            backTrace(nums,i+1,list,res);
            list.remove(list.size()-1);
        }
    }
}

【第十四题】检查子树

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

分析:

class Solution {
    public boolean checkSubTree(TreeNode t1, TreeNode t2) {
        if(t1 == null){return false;}
        //假如两棵树的根节点相同,那么就继续向下递归比较
        if(t1.val == t2.val){
            if(digui(t1,t2)){
                return true;
            }
        }
        //假如根节点不同,那么就在t1的左右子树递归匹配
        return checkSubTree(t1.left,t2) || checkSubTree(t1.right,t2);
    }
    public boolean digui(TreeNode t1,TreeNode t2){
        if(t1 == null && t2 == null){return true;}
        if(t1 == null || t2 == null || t1.val!=t2.val){return false;}
        return digui(t1.left,t2.left) && digui(t1.right,t2.right);
    }
}

【第十五题】阶乘尾数

【Leecode笔记】第二十四周 剑指offer(3.29-4.4)
分析:因为末尾的0是由是否是10的倍数决定,而是否是10——>是否有2*5——>多少个5(因为偶数的个数肯定比5多,因此只需要计算5的个数即可)。
【Leecode笔记】第二十四周 剑指offer(3.29-4.4)

class Solution {
    public int trailingZeroes(int n) {
		int count = 0;
		while(n >= 5){
			n /= 5;
			count+=n;
		}
		return count;
	}
}
上一篇:LeeCode 206. 反转链表


下一篇:leecode刷题总结--图