刷题篇--热题HOT 81-90

337.打家劫舍Ⅲ

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:输入: [3,2,3,null,3,null,1]

     3
    / \
   2   3
    \   \
     3   1

输出: 7 解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]

     3
    / \
   4   5
  / \    \
 1   3   1
输出: 9解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
分析:二叉树,递归,当前节点是否抢劫取决于Max{ 当前节点抢+当前节点的左右子节点的左右子节点的最大金额和(可枪可不抢) , 当前节点不抢+抢当前节点左右子节点}

 1 /**
 2  * Definition for a binary tree node.
 3  * public class TreeNode {
 4  *     int val;
 5  *     TreeNode left;
 6  *     TreeNode right;
 7  *     TreeNode(int x) { val = x; }
 8  * }
 9  */
10 class Solution {
11     //使用一个哈希表存储已经访问过的节点,防止重复访问
12     Map<TreeNode,Integer> map = new HashMap();
13     public int rob(TreeNode root) {
14         if(root==null) return 0;
15         if(map.containsKey(root)) return map.get(root);
16         int res = 0;
17         int do_it = root.val 
18                   + (root.left==null?0:rob(root.left.left)+rob(root.left.right)) 
19                   + (root.right==null?0:rob(root.right.left)+rob(root.right.right));
20         int do_not = rob(root.left) + rob(root.right);        
21         res = Math.max(do_it,do_not);
22         map.put(root,res);//存储当前节点结果
23         return res;
24     }
25 }
 1 class Solution {//法二
 2     public int rob(TreeNode root) {
 3         int[] res = helper(root);
 4         return Math.max(res[0],res[1]);
 5     }
 6     //设置一个大小为2的数组arr,arr[0]表示不抢root获得的最大金额,arr[1]表示抢root获得的最大金额。
 7     int[] helper(TreeNode root){
 8         if(root==null) return new int[]{0,0};//如果当前节点为null,抢不抢都是0
 9         int[] left = helper(root.left);//记录左节点抢和不抢的两种结果
10         int[] right = helper(root.right);//记录右节点抢和不抢的两种结果
11         int rob = root.val+left[0]+right[0];
12         int not_rob = Math.max(left[0],left[1]) + Math.max(right[0],right[1]);//可抢可不抢,看哪种情况大,择优抢劫
13         return new int[]{not_rob, rob};
14     }
15 }

338.比特位计数

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
示例 1:输入: 2   输出: [0,1,1]
示例 2:输入: 5  输出: [0,1,1,2,1,2]
分析:如何判断一个数的1的个数?先&1,再>>1。进阶里说到可使用线性时间内O(N)就可以完成,那么可以想一下一次遍历完成,不在嵌套循环。那么之后的数与之前的数之间一定存在关系,由之前的可以推出之后的。

0-0-0        8-1000-1

1-1-1        9-1001-2

2-10-1      10-1010-2

3-11-2      11-1011-3

4-100-1    12-1100-2

5-101-2    13-1101-3

6-110-2    14-1110-3

7-111-3     15-1111-4

由上面可以看出,8~15二进制1的个数比0~7二进制个数各多一个,4~7二进制1的个数比0~4二进制个数个多一个…1比0二进制1的个数多一个。

可以推出P(i) = p(i/2)+(i mod 2).

 1 class Solution {
 2     public int[] countBits(int num) {
 3         int[] res = new int[num+1];
 4         //res[0]默认是0
 5         for(int i=1;i<=num;i++){
 6             res[i] = res[i>>1] + (i&1);
 7         } 
 8         return res;
 9     }
10 }

347.前K个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:输入: nums = [1,1,1,2,2,3],   k = 2  输出: [1,2]
示例 2:输入: nums = [1],   k = 1  输出: [1]
说明:你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
分析:计算出现频率可以使用哈希表线性时间内就可以计算,然后进行排序,题目要求O(nlogn),又是前k高,可以使用容量为K的小顶堆存储前K高的元素,遍历哈希表时如果value大于堆顶元素,则剔除堆顶,添加新元素。O(nlogk)

 1 class Solution {
 2     public List<Integer> topKFrequent(int[] nums, int k) {
 3         //使用哈希表存储元素出现频率
 4         Map<Integer,Integer> map = new HashMap();
 5         for(int num:nums){
 6             if(map.containsKey(num)){
 7                 map.put(num,map.get(num)+1);
 8             }else{
 9                 map.put(num,1);
10             }
11         }
12         //创建一个容量为k的小顶堆
13         PriorityQueue<Integer> heap = new PriorityQueue(new Comparator<Integer>(){
14             public int compare(Integer a,Integer b){
15                 return map.get(a)-map.get(b);//因为要存入map的key,比较value
16             }
17         });
18         //遍历哈希表
19         for(int key:map.keySet()){
20             if(heap.size()<k){
21                 heap.add(key);
22             }else if(map.get(key)>map.get(heap.peek())){
23                 heap.remove();
24                 heap.add(key);
25             }
26         }
27         //遍历最小堆,取出数据
28         List<Integer> res = new ArrayList();
29         while(!heap.isEmpty()){
30             res.add(heap.remove());
31         }
32         return res;
33     }
34 }

法二:桶排序,依然使用哈希表存储各元素出现的次数,但是不再使用小顶堆,而是使用一个数组来存储结果,新数组索引就是哈希表的value,即原数组元素出现的次数,新数组的值就是哈希表的key,即原数组的元素。因为会出现频率相同的两个值,因此设置数组是一个List类型的。这样根据新数组索引和K便可以返回频率前K个高的元素。O(N)

 1 class Solution {
 2     public List<Integer> topKFrequent(int[] nums, int k) {
 3         //使用哈希表存储元素出现频率
 4         Map<Integer,Integer> map = new HashMap();
 5         for(int num:nums){
 6             if(map.containsKey(num)){
 7                 map.put(num,map.get(num)+1);
 8             }else{
 9                 map.put(num,1);
10             }
11         }
12         //建立一个数组,新数组索引就是哈希表的value,即原数组元素出现的次数,新数组的值就是哈希表的key,即原数组的元素
13         List<Integer>[] tmp = new List[nums.length+1];
14         //遍历哈希表
15         for(int key:map.keySet()){
16             int index = map.get(key);
17             if(tmp[index]==null){
18                 tmp[index] = new ArrayList();
19             }
20             tmp[index].add(key);
21         }
22         //遍历数组tmp(从后往前),取出数据
23         List<Integer> res = new ArrayList();
24         for(int i=tmp.length-1;i>=0&&res.size()<k;i--){
25             if(tmp[i]==null) continue;
26             res.addAll(tmp[i]);
27         }
28         return res;
29     }
30 }

394.字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例:s = "3[a]2[bc]", 返回 "aaabcbc".  s = "3[a2[c]]", 返回 "accaccacc".  s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef".
分析:字符串特点每个左括号前必是一个数字,括号中间必是字符。类似这种带括号的,使用栈来计算。

构建辅助栈 stack, 遍历字符串 s 中每个字符 c;
    ① 当 c 为数字时,将数字字符转化为数字 multi,用于后续倍数计算;
    ② 当 c 为字母时,在 res 尾部添加 c;
    ③ 当 c 为 [ 时,将当前 multi 和 res 入栈,并分别置 0置空:
          (记录此 [ 前的临时结果 res 至栈,用于发现对应 ] 后的拼接操作;
          记录此 [ 前的倍数 multi 至栈,用于发现对应 ] 后,获取 multi × [...] 字符串。
          进入到新 [ 后,res 和 multi 重新记录。)
    ④ 当 c 为 ] 时,stack 出栈,拼接字符串 res = last_res + cur_multi * res,其中:
          (last_res是上个 [ 到当前 [ 的字符串,例如 "3[a2[c]]" 中的 a;
          cur_multi是当前 [ 到 ] 内字符串的重复倍数,例如 "3[a2[c]]" 中的 2。)
返回字符串 res。

 1 class Solution {
 2     public String decodeString(String s) {
 3         String res = "";
 4         int multi = 0;
 5         Stack<String> stack_res = new Stack();
 6         Stack<Integer> stack_multi = new Stack();
 7         for(Character c : s.toCharArray()){
 8             if(c=='['){
 9                 stack_multi.push(multi);
10                 stack_res.push(res);
11                 multi=0;
12                 res="";
13             }else if(c==']'){
14                 int cur_multi = stack_multi.pop();
15                 String str = "";
16                 for(int i=0;i<cur_multi;i++){
17                     str = str+res; 
18                 }
19                 res = stack_res.pop()+str;
20             }else if(c>='0'&&c<='9'){
21                 //连续数字情况
22                 multi = multi*10+Integer.parseInt(c+"");
23             }else{
24                 res=res+c;
25             }
26         }
27         return res;
28     }
29 }

399.除法求值
给出方程式 A / B = k, 其中 A 和 B 均为代表字符串的变量, k 是一个浮点型数字。根据已知方程式求解问题,并返回计算结果。如果结果不存在,则返回 -1.0。
示例 :给定 a / b = 2.0, b / c = 3.0       问题: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?              返回 [6.0, 0.5, -1.0, 1.0, -1.0 ]
  输入为: vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries(方程式,方程式结果,问题方程式), 其中 equations.size() == values.size(),即方程式的长度与方程式结果长度相等(程式与结果一一对应),并且结果值均为正数。以上为方程式的描述。 返回vector<double>类型。
  基于上述例子,输入如下:equations(方程式) = [ ["a", "b"], ["b", "c"] ],       values(方程式结果) = [2.0, 3.0],          queries(问题方程式) = [ ["a", "c"], ["b", "a"], ["a", "e"], ["a", "a"], ["x", "x"] ]. 输入总是有效的。你可以假设除法运算中不会出现除数为0的情况,且不存在任何矛盾的结果。

分析:可以看成有向图,a/b=2.0,b->a权值为2.0,b/c=3.0,c->b权值为3.0。

406.根据身高重建队列

假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。注意:总人数少于1100人。
示例  输入:[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]  输出:[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]。

分析:重建完的队列要满足前面不低于自己身高的人为k个。核心思想:想让高个子人先站好队,然后矮个子的人插入到K位置上,那么前面一定有K个比自己高的人了,矮个子插入对高个子没影响。例如当除了[4,4]其他人都排好时,[4,4]插入哪都对其他人没有影响,只要保证自己前面有4个人就行。所以为了先排高后排矮,需要将数组进行排序,按照身高h降序,k升序思想排序。

// [7,0], [7,1], [6,1], [5,0], [5,2], [4,4]
        // 再一个一个插入。
        // [7,0]
        // [7,0], [7,1]
        // [7,0], [6,1], [7,1]
        // [5,0], [7,0], [6,1], [7,1]
        // [5,0], [7,0], [5,2], [6,1], [7,1]
        // [5,0], [7,0], [5,2], [6,1], [4,4], [7,1]
 1 class Solution {
 2     public int[][] reconstructQueue(int[][] people) {
 3         Arrays.sort(people,new Comparator<int[]>(){
 4             public int compare(int[] o1,int[] o2){
 5                 return o1[0]==o2[0]?o1[1]-o2[1]:o2[0]-o1[0];
 6             }
 7         });
 8         List<int[]> res = new ArrayList();
 9         for(int[] p : people){
10             res.add(p[1],p);
11         }
12         return res.toArray(new int[people.length][2]);
13     }
14 }

416.分割等和子集

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:每个数组中的元素不会超过 100,数组的大小不会超过 200
示例 1:  输入: [1, 5, 11, 5]  输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:  输入: [1, 2, 3, 5]  输出: false
解释: 数组不能分割成两个元素和相等的子集.

分析:如果一个数组可以分割成两个元素相等的子集,那么只要能够找出一个等于数组元素和一半的子集即可,且数组和sum一定是个偶数。这个问题现在就变成了能否在数组中找到和为sum/2的子集。创建一个dp[i]数组,数组是否存在一个和为k的子集.

 1 class Solution {
 2     public boolean canPartition(int[] nums) {
 3         int len = nums.length;
 4         int sum = 0;
 5         for(int num:nums){
 6             sum+=num;
 7         }
 8         if(sum%2==1) return false;
 9         int half = sum>>1;
10         boolean[] dp = new boolean[half+1];
11         dp[0] = true;
12         for(int num:nums){
13             for(int i=half;i>=num;i--){
14                 dp[i] = dp[i] || dp[i-num];
15             }
16         }
17         return dp[half];
18     }
19 }

 437.路经总和

给定一个二叉树,它的每个结点都存放着一个整数值。找出路径和等于给定数值的路径总数。路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。
示例:root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
      10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1
返回 3。和等于 8 的路径有:
1.  5 -> 3
2.  5 -> 2 -> 1
3.  -3 -> 11
分析:回溯递归,本题难点在于路径起点不在根节点,且尾节点不是叶子节点。本题使用一个列表存储遍历过的值。

 1 /**
 2  * Definition for a binary tree node.
 3  * public class TreeNode {
 4  *     int val;
 5  *     TreeNode left;
 6  *     TreeNode right;
 7  *     TreeNode(int x) { val = x; }
 8  * }
 9  */
10 class Solution {
11     private int count = 0;
12     public int pathSum(TreeNode root, int sum) {
13         helper(root,sum,new ArrayList<Integer>());
14         return count;
15     }
16     public void helper(TreeNode root, int sum, ArrayList<Integer> list){
17         if(root==null) return;
18         list.add(root.val);
19         int cur_sum = 0;
20         //从当前节点向根节点找是否存在和为sum的路径
21         for(int i=list.size()-1;i>=0;i--){
22             cur_sum += list.get(i);
23             if(cur_sum == sum) count++;
24         }
25         helper(root.left,sum,list);
26         helper(root.right,sum,list);
27         list.remove(list.size()-1);//回溯
28     }
29 }

438.找到字符串中所有字母异位词

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:字母异位词指字母相同,但排列不同的字符串。不考虑答案输出的顺序。
示例 1:  输入:s: "cbaebabacd" p: "abc"  输出:[0, 6]
解释:起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。
示例 2:  输入:s: "abab" p: "ab"  输出:[0, 1, 2]
解释:起始索引等于 0 的子串是 "ab", 它是 "ab" 的字母异位词。起始索引等于 1 的子串是 "ba", 它是 "ab" 的字母异位词。起始索引等于 2 的子串是 "ab", 它是 "ab" 的字母异位词。
分析:

1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引闭区间 [left, right] 称为一个「窗口」。
2、我们先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
3、此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。
4、重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。

       
如何判断字符串中符合要求了呢?我们可以使用两个数组或者哈希表,一个数组needs记录字符串p中包含的字符出现的次数,另一个数组window记录当前窗口中包含的字符及出现的次数,如果window包含所有need的字符(数组索引),并且数组值大于等于needs中的值,那么就可以知道当前窗口符合要求了。

 1 class Solution {
 2     public List<Integer> findAnagrams(String s, String p) {
 3         if(s==null||s.length()==0) return new ArrayList<Integer>();
 4         ArrayList<Integer> res = new ArrayList<Integer>();
 5         int[] needs = new int[26];
 6         int[] window = new int[26];
 7         //记录p中字符的次数
 8         for(char c : p.toCharArray()){
 9             needs[c-'a']++;
10         }
11         int left = 0, right = 0, match = 0;//match用来记录窗口中匹配的字符个数,如果match等于p长度,那么当前窗口包含异位词
12         while(right < s.length()){
13             char c = s.charAt(right);
14             if(needs[c-'a']>=0){//说明s中该字符是p出现过的,那么window中该位置加一
15                 window[c-'a']++;
16                 //如果window数组值等于needs中的值,说明有一个匹配了
17                 if(window[c-'a'] == needs[c-'a']){
18                     match++;
19                 }
20             }
21             //统计p中字符种类
22             int count =0;
23             for(int need:needs){
24                 if(need>0) count++;
25             }
26             //左移
27             while(match == count){
28                 if(right-left+1==p.length()){
29                     res.add(left);
30                 }
31                 char c2 = s.charAt(left);
32                 if(needs[c2-'a']>0){//说明要被左移的出现在p中
33                     window[c2-'a']--;//相应的window减一
34                     //判断是否减去match
35                     if(window[c2-'a']<needs[c2-'a']){
36                         match--;
37                     }
38                 }
39                 left++;
40             }
41             right++;
42         }
43         return res;
44     }
45 }

 448.找到所有数组中消失的数字

给定一个范围在  1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。找到所有在 [1, n] 范围之间没有出现在数组中的数字。您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
示例:输入:[4,3,2,7,8,2,3,1]  输出:[5,6]
方法一:使用哈希表存储遍历过的数组元素。key存储数组元素,value存储出现次数。最后遍历哈希表,如果n之前的key出现次数是0,那么添加到集合中。
方法二:修改原数组(如果一个数组没有消失的数字,那么排序后元素i的索引就是i-1)。每当遍历到一个新的数组元素n时,将数组n-1位置的值取反,表示n出现过,通过这次遍历,只要在数组中出现过的元素n,都可以通过对应n-1位置值是否为负数来判定。

 1 class Solution {
 2     public List<Integer> findDisappearedNumbers(int[] nums) {
 3         List<Integer> res = new ArrayList<Integer>();
 4         for(int num : nums){
 5             if(nums[Math.abs(num)-1] > 0) {
 6                 nums[Math.abs(num)-1] = -nums[Math.abs(num)-1];
 7             }
 8         }
 9         for(int i=0;i<nums.length;i++){
10             if(nums[i] > 0){
11                 res.add(i+1);
12             }
13         }
14         return res;
15     }
16 }

猜你喜欢

转载自www.cnblogs.com/qmillet/p/12179616.html