Leetcode分类刷题答案&心得

Posted KINGHEY

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode分类刷题答案&心得相关的知识,希望对你有一定的参考价值。

Array

448.找出数组中所有消失的数

要求:整型数组取值为 1 ≤ a[i] ≤ n,n是数组大小,一些元素重复出现,找出[1,n]中没出现的数,实现时时间复杂度为O(n),并不占额外空间

思路1:(discuss)用数组下标标记未出现的数,如出现4就把a[3]的数变成负数,当查找时判断a的正负就能获取下标

tips:注意数组溢出

    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> disList = new ArrayList<Integer>();
        
        //用数组下标来记录出现过的数
        for (int i = 0; i < nums.length; i++) {
            int item = Math.abs(nums[i]) - 1;
            //判断是否已经出现过
            if (nums[item] > 0) {
                nums[item] = -nums[item];
            }
        }
        
        //把仍然是正数所对应的下标加入数组列表中
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > 0) {
                disList.add(i + 1);
            }
        }
        return disList;
    }
View Code

思路2:(discuss)下标标记数,但把出现过的数用作下标,然后此下标对应的数+n

    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> disList = new ArrayList<Integer>();
        int n = nums.length;
        //用数组下标来记录出现过的数
        for (int i = 0; i < nums.length; i++) {
            nums[(nums[i] - 1) % n] += n;
        }
        
        //把a[i]小于n时的i+1加入数组列表中
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] <= n) {
                disList.add(i + 1);
            }
        }
        return disList;
    }
View Code

思路3:(discuss)整理数组,目的就是把数字与下标对应,整理后下标与数字不对应就证明此数消失,此方法称为数组原地实现(只改变顺序不改变值)

    public List<Integer> findDisappearedNumbers(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            while (nums[i] != i + 1 && nums[i] != nums[nums[i] - 1]) {
                int tmp = nums[i];
                nums[i] = nums[tmp - 1];
                nums[tmp - 1] = tmp;
            }
        }
        List<Integer> res = new ArrayList<Integer>();
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != i + 1) {
                res.add(i + 1);
            }
        }
        return res;
    }
View Code

 2016-12-14

 

442.找出数组重复的数

要求与448类似

思路1:(自写一次AC)还是用下标标记,每出现一次,相应下标的数+n,检索时搜大于2n的就重复两次,还能检测出现次数大于两次的情况

    public List<Integer> findDuplicates(int[] nums) {
        List<Integer> dupList = new ArrayList<Integer>();
        if (nums == null || nums.length == 0) {
            return dupList;
        }
        int n = nums.length;
        //出现一次就把相应下标所指整数+n
        for (int i = 0; i < n; i++) {
            nums[(nums[i] - 1) % n] += n;
        }
        for (int i = 0; i < n; i++) {
            if (nums[i] > 2*n) {
                dupList.add(i + 1);
            }
        }
        return dupList;
    }
View Code

思路2:(discuss)用正负法,巧用if的位置,但只适合出现两次,注意Math.abs用了两次(另:再遍历一次+Math.abs可恢复原数据)

    public List<Integer> findDuplicates(int[] nums) {
        List<Integer> dupList = new ArrayList<Integer>();
        if (nums == null || nums.length == 0) {
            return dupList;
        }
        for (int i = 0; i < nums.length; i++) {
            //获得原下标
            int item = Math.abs(nums[i]) - 1;
            //只有出现过一次并且item一样才会满足
            if (nums[item] < 0) {
                dupList.add(Math.abs(item + 1));
            }
            nums[item] = -nums[item];
        }
        return dupList;
    }
View Code

2016-12-14

 

414.第三大的数

要求:给出非空整型数组,返回第三大的数;若不存在,则返回最大数。时间复杂度要为O(n)

思路1: (discuss)尝试多次才成功,定义前三大数,并用continue跳出循环排除重复的情况,用右移插入数,判断好是否到第三大

warn:想好如何定义前三大的数,若定义为Integer.MIN_VALUE,输入为-2147483648时出错,可以投机定义为long类型

    public int thirdMax(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;    
        }
        //创建三个变量存储前三大的数
        long max, mid, min;
        max = mid = min = Long.MIN_VALUE;
        //判断第三个数是否赋值
        int count = 0;
        
        for (int x : nums) {
            //防止重复输入,continue跳出本次循环
            if (x == max || x == mid) {
                continue;
            }
            //x大于max则三个变量都向右移
            if (x > max) {
                min = mid;
                mid = max;
                max = x;
                count++;
            } else if (x > mid) {
                min = mid;
                mid = x;
                count++;
            } else if (x >= min) {
                min = x;
                count++;
            }
        }
        if (count >= 3) {
            return (int)min;
        }
        return (int)max;
    }
View Code

 思路2: (discuss)使用TreeSet特性,有序且不能重复,当size()大于3时去掉第一位,小于3时输出最大值,时间复杂度为O(nlog3)

    public int thirdMax(int[] nums) {
        TreeSet<Integer> set = new TreeSet<Integer>();
        for (int num : nums) {
            set.add(num);
            //超过3个则去除
            if (set.size() > 3) {
                set.remove(set.first());
            }
        }
        //少于3个输出最大值
        return set.size() < 3 ? set.last() : set.first();
View Code

 2016-12-15

 

283.移动零

要求:把数组中的0都放在末尾,且保持其他数的相对位置

思路1: (自写一次AC)从末尾开始判断是否为0,是0就与旁边交换,直到旁边的不是0为止(24ms)

tips:注意超过数组长度

    public void moveZeroes(int[] nums) {
        for (int i = nums.length - 1; i >= 0; i--) {
            if (nums[i] == 0) {
                int j = i;
                while (j < nums.length - 1 && nums[j + 1] != 0) {
                    nums[j] = nums[j + 1];
                    nums[j + 1] = 0;
                    j++;
                }
            }
        }
    }
View Code

思路2: (discuss)把非0数放在头,其余位置补齐0(0ms)

    public void moveZeroes(int[] nums) {
        int nonzero = 0;
        for (int num : nums) {
            if (num != 0) {
                nums[nonzero] = num;
                nonzero++;
            }
        }
        //补全0
        while (nonzero < nums.length) {
            nums[nonzero] = 0;
            nonzero++;
        }
    }
View Code

思路3: (discuss)一指针一直动判断是否为0,另一指针停留在非0头部的下一位处等待非0数的出现,从而进行交换(0ms)

    public void moveZeroes(int[] nums) {
        //指向非0数的指针
        int j = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                int temp = nums[j];
                nums[j] = nums[i];
                nums[i] = temp;
                j++;
            }
        }
    }
View Code

 2016-12-15

 

268.消失的数

要求:数组包含n个数,从0...n取值,找出缺失的那个值,时间复杂度要线性,空间复杂度恒定

思路1: (九章答案+自己想)把下标和值对应起来,等于是排序,然后判断下标和值不同的就是缺失项(对应448的思路3)

warn:记得用while,直到下标和值能对应,由于没有n这个下标,所以要排除这种情况,前面的都没缺失就返回n

    public int missingNumber(int[] nums) {
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            //先排除n的情况,因为超出了长度
            while (nums[i] != i && nums[i] < n) {
                int t = nums[i];
                nums[i] = nums[t];
                nums[t] = t;
            }
        }
        for (int i = 0; i < n; i++) {
            if (nums[i] != i) {
                return i;
            }
        }
        return n;
    }
View Code

思路2: (discuss)利用数学,比如求和,下标与值求异或(a ^ b ^ b = a,剩下单个的)

求和法:不缺失数组和(n+1项)减去缺失数组和(n项)

    public int missingNumber(int[] nums) {
        int n = nums.length;
        int s = n * (n + 1) / 2;
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += nums[i];
        }
        return s - sum;
    }
View Code

异或法:先设xor为n,数组有n时与n抵消可以输出缺失那一项,无n时就代表缺少n

    public int missingNumber(int[] nums) {
        //下标没有n项,所以把xor先设为n
        int xor = nums.length;
        for (int i = 0; i < nums.length; i++) {
            xor = xor ^ i ^ nums[i];
        }
        //数组的值有n时与原始xor抵消,输出缺失的另一项,无n就输出n
        return xor;
    }
View Code

2016-12-15

 

238.排除自己后的乘积

要求:output数组上每个位置的值等于由原数组num除该位置外的所有数的乘积,不能使用除法,复杂度为O(n)

思路: (discuss)分成位置的两边相乘

tips:从左往右乘的时候,令output[0]为1,此后就可以把output[i - 1]作为是之前i - 1项的积,可重复利用;而从右往左时,第一个数已经是最终结果,而第二个数只需要乘一次...所以需要新的累积变量right

        (其实也可以两个方向都用累积变量存储前面项的乘积,只不过左往右乘的时候可应用输出数组本身作为累积,更省空间)

    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] output = new int[n];
        output[0] = 1;
        
        //从i=1开始,先算nums[i]左边数的乘积
        for (int i = 1; i < n; i++) {
            output[i] = output[i - 1] * nums[i - 1];
        }
        //新的累加值,算右边的乘积
        int right = 1;
        for (int i = n - 1; i >= 0; i--) {
            output[i] *= right;
            right *= nums[i];
        }
        return output;
    }
View Code

2016-12-15

 

229.多数元素(众数)II

要求:在大小为n的数组中找出出现次数大于n / 3次的元素,用线性时间和O(1)空间

思路: (经典算法)“摩尔投票法”,这里的含义是先设置两个候选数,相同的时候就计1票,不同的时候就扣1票,当票数为0时,就换另一个候选数,并且计第1票;选好候选数之后,就真正计算各自的票数

tips:背下来思路...1、设候选数、计票器 2、确定候选数 3、真正计票 4、判断是否满足条件

warn:考虑空数组,数组长度为1,两个数相等的情况!!!

    public List<Integer> majorityElement(int[] nums) {
        List<Integer> res = new ArrayList<Integer>();
        int n = nums.length;
        if (nums == null || n == 0) {
            return res;
        }
        if (n == 1) {
            res.add(nums[0]);
            return res;
        }
        
        //设2个候选数,2个对应的计票器
        int number1, number2, count1, count2;
        number1 = number2 = nums[0];
        count1 = count2 = 0;
        
        //挑选候选数
        for (int i = 0; i < n; i++) {
            if (nums[i] == number1) {
                count1++;
            } else if (nums[i] == number2) {
                count2++;
            } else if (count1 == 0) {  //count减为0就换候选数
                number1 = nums[i];
                count1++;
            } else if (count2 == 0) {
                number2 = nums[i];
                count2++;
            } else {
                count1--;
                count2--;
            }
        }
        
        //清空票数,计算两个候选数的真正票数
        count1 = count2 = 0;
        for (int i = 0; i < n; i++) {
            if (nums[i] == number1) {
                count1++;
            }
            if (nums[i] == number2) {
                count2++;
            }
        }
        
        //判断票数是否达到n/3以上
        if (count1 > n / 3) {
            res.add(number1);
        }
        //考虑两数相等的情况
        if (count2 > n / 3 && number2 != number1) {
            res.add(number2);
        }
        return res;
    }
View Code

另:据说使用Map也行,但是空间复杂度比较大

2016-12-15 

 

228.总结范围数

要求:数组不重复,出现连续的数列就用->表示,如0, 1, 2就变成了0->2,单个数不需改变

思路: (自写修改后AC)判断nums[i+1] == nums[i],若等于则while循环获得范围结尾处,不等于就直接输出当前。

warn:注意数组溢出,注意数组溢出,注意数组溢出!!!考虑数组长度0,1,以及n-1位置上的情况

    public List<String> summaryRanges(int[] nums) {
        List<String> res = new ArrayList<String>();
        int n = nums.length;
        if (nums == null || n == 0) {
            return res;
        }
        if (n == 1) {
            String s = String.valueOf(nums[0]);
            res.add(s);
            return res;
        }
        for (int i = 0; i < n - 1; i++) {
            if (nums[i + 1] == nums[i] + 1) {
                int j = i;
                while (i < n - 1 && nums[i + 1] == nums[i] + 1) {
                    i++;
                }
                String s = nums[j] + "->" + nums[i];
                res.add(s);
            } else {
                String s = String.valueOf(nums[i]);
                res.add(s);
            }
        }
        if (nums[n - 1] != nums[n - 2] + 1) {
            String s = String.valueOf(nums[n - 1]);
            res.add(s);
        }
        return res;
    }
View Code

 2016-12-16

 

219.包含重复II

要求:给出数组和整数k,求是否在最大间隔j - i = k以内有重复数出现

思路1: (查HashSet用法后自写一次AC)根据HashSet特性,不能有重复数插入,让set长度为k + 1(头尾下标间隔为k,总共包含k + 1个数)

tips:超过长度删除数据时,虽set无序,但可根据输入数组的有序,来确定删除的是哪个数(输入nums[i]对应删除nums[i - k - 1])

    public boolean containsNearbyDuplicate(int[] nums, int k) {
        if (nums == null || nums.length == 0 || k == 0) {
            return false;
        }
        Set<Integer> set = new HashSet<Integer>();
        for (int i = 0; i <nums.length; i++) {
            //set长度超过k,则删除当前第1个
            if (i > k) {
                //set无序,但可利用数组有序
                set.remove(nums[i - 1 - k]); 
            }
            //插入不了证明重复
            if (!set.add(nums[i])) {
                return true;
            }
        }
        return false;
    }
View Code

 思路2: (discuss)根据HashMap特性键不能重复,把数组的值当作map键,数组的下标当作map值,求长度时比较map值(对应数组下标)

tips:先判断是否已经存在再输入!!

    public boolean containsNearbyDuplicate(int[] nums, int k) {
        if (nums == null || nums.length == 0 || k == 0) {
            return false;
        }
        Map<Integer, Integer> map = new HashMap();
        for (int i = 0; i < nums.length; i++) {
            if (map.containsKey(nums[i])) {
                //get(nums[i])是找之前的
                if (i - map.get(nums[i]) <= k) {
                    return true;
                }
            }
            //调转键与值
            map.put(nums[i], i);
        }
        return false;
    }
View Code

2016-12-16

 

217.包含重复

要求:找出数组是否有重复数

思路: (自写map和set的)与219一样,比较简单,只放了map代码

    public boolean containsDuplicate(int[] nums) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = 0; i < nums.length; i++) {
            if (map.containsKey(nums[i])) {
                return true;
            }
            map.put(nums[i], i);
        }
        return false;
    }
View Code

 2016-12-16

 

216.组合和III

要求:用k个1-9的数,求所有加起来等于n的集合,每个集合中的元素不重复

思路: (经典算法)“BackTracking回溯算法”,满足条件就添加,不满足就继续递归

warn:背步骤...具体还没理解如何return,如何递归  backTracking:1、判断是否满足条件,是就添加并返回 2、循环:2.1、添加新循环元素 2.2、递归表达式 2.3、弹出最后一项

    public List<List<Integer>> combinationSum3(int k, int n) {
        List<List<Integer>> ans = new ArrayList<>();
        backTracking(ans, new ArrayList<Integer>(), k, 1, n);
        return ans;
    }
    
    private void backTracking(List<List<Integer>> ans, List<Integer> comb, int k, int start, int n) {
        //数组中有k个数并相加为n就返回
        if (comb.size() == k && n == 0) {
            List<Integer> real = new ArrayList<Integer>(comb);
            ans.add(real);
            return;
        }
        for (int i = start; i <= 9; i++) {
            //添加新数
            comb.add(i);
            //递归
            backTracking(ans, comb, k, i+1, n-i);
            //弹出最后一位
            comb.remove(comb.size() - 1);
        }
    }
View Code

 2016-12-17

 

209.最小子数组之和

要求:给出含有n个正数的数组,用最短的子数组各项加起来能够大于等于给定的正数s,若找不到则返回0

思路1: (discuss)“Two Pointers”用两个指针和一个记录最短长度的min,首先第一个指针用来叠加,第二个指针用来大于s时减去前面的数,并且更新最短长度min,时间复杂度为O(n)(虽然用了两层while)

    public int minSubArrayLen(int s, int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int i = 0, j = 0, sum = 0, min = Integer.MAX_VALUE;
        while (i < nums.length) {
            sum += nums[i++];
            while (sum >= s) {
                //超过s就减去前面一位,保留最短长度
                min = Math.min(min, i - j);
                sum -= nums[j++];
            }
        }
        return min == Integer.MAX_VALUE ? 0 : min;
    }
View Code

思路2:(discuss)叠加和法

第一个循环先求出sums数组,其中sums[i] = sums[i - 1] + nums[i - 1],此数组i项代表前i项的累计和

第二个循环用二分法查找sums[i]+s的位置end,即代表sums[end] - sums[i] = s,此时子数组长度是end-i,复杂度为O(nlogn)

    public int minSubArrayLen(int s, int[] nums) {
        int[] sums = new int[nums.length + 1];
        //求前i项和
        for (int i = 1; i < sums.length; i++) {
            sums[i] = sums[i - 1] + nums[i - 1];
        }
        int minLen = Integer.MAX_VALUE;
        for (int i = 0; i < sums.length; i++) {
            //二分法找累计和的差值为s的位置end
            int end = binarySearch(i + 1, sums.length - 1, sums[i] + s, sums);
            if (end == sums.length) break;
            if (end - i < minLen) minLen = end - i;
        }
        return minLen == Integer.MAX_VALUE ? 0 : minLen;
    }
    
    private int binarySearch(int lo, int hi, int key, int[] sums) {
        while (lo <= hi) {
           int mid = (lo + hi) / 2;
           if (sums[mid] >= key){
               hi = mid - 1;
           } else {
               lo = mid + 1;
           }
        }
        return lo;
    }
View Code

 2016-12-17

 

189.旋转数组

要求:旋转n长度的数组,位数为k,用三种以上方法,其中最好消耗O(1)额外空间(即原地)

 思路1: (九章思路+自写)假如原数组为1234567,旋转度为3,变成5671234,就从第4、5位中间(nums.length - k处)分割,进行三次首尾反转,前面1234首尾反转为4321,后面变成765,再统一首尾反转一次

warn:注意k大于数组长度的情况,用k %= nums.length解决

    public void rotate(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return;
        }
        //防止k大于数组长度
        k %= nums.length;
        

以上是关于Leetcode分类刷题答案&心得的主要内容,如果未能解决你的问题,请参考以下文章

leetcode刷题记录(JAVA&Python)

[JavaScript 刷题] 搜索 - leetcode 200 & 695

LeetCode刷题笔记-数据结构-day2

leetcode分类刷题(续2)

leetcode分类刷题

leetcode分类刷题(续1)