力扣刷题笔记·面试经典150题

LeetCode数组、链表等算法题思路与解法

88.合并两个有序数组

思路

本题考虑从后往前遍历,设置三个指针指向两个数组和新数组的最后一个位置,每次比较两个数组指向的数值大小,大的放到新数组指向位置,然后大的指针和新数组指针减1操作。
时间复杂度:O(m+n) 相当于两个数组都遍历了因此是m+n
空间复杂度:O(1)

注:必须两个数组都是有序的才可以!

代码:

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p1=m-1;
        int p2=n-1;
        int p=n+m-1;
        while(p2>=0){//nums2还有要合并的元素
            //如果p1<0,那么走else,把nums2合并到nums1
            if(p1>=0&&nums1[p1]>nums2[p2]){
                nums1[p--]=nums1[p1--];
            }else{
                nums1[p--]=nums2[p2--];
            }
        }
    }
}

27.移除元素

思路

当遍历到等于val的元素,设置j=i+1,直到找到nums[j]不等于val然后两者交换,如果j就是数组长度,则说明当前i以后都是val了,i返回即可。
时间复杂度:O(n2n^2n2)

代码如下:

class Solution {
    public int removeElement(int[] nums, int val) {
        for(int i=0;i<nums.length;i++){
            if(nums[i]==val){
                int j=i;
                while(j<nums.length&&nums[j]==val){
                    j++;
                }
                if(j==nums.length) return i;
                int temp=nums[j];
                nums[j]=nums[i];
                nums[i]=temp;
            }
            
        }
        return nums.length;
    }
}

这里有一种**时间复杂度为O(n)**的算法,即快慢指针。快指针不断寻找不为val的值,如果不为val则进行交换操作,慢指针则只有进行交换操作时才会移动。
代码如下:

class Solution {
    public int removeElement(int[] nums, int val) {
        int slow=0;
        for(int fast=0;fast<nums.length;fast++){
            if(nums[fast]!=val){
                nums[slow]=nums[fast];
                slow++;
            }
        }
        return slow;
    }
}

26.删除有序数组中的重复项

与上一题一样,这里考虑快慢指针的解法,快指针不断寻找不等于慢指针对应的值,如果不等于则先慢指针后移一位再交换,最终返回slow+1。
时间复杂度O(n)
代码如下:

class Solution {
    public int removeDuplicates(int[] nums) {
        int slow=0;
        for(int fast=slow+1;fast<nums.length;fast++){
            if(nums[fast]!=nums[slow]){
                nums[++slow]=nums[fast];
            }
        }
        return slow+1;
    }
}

80.删除有序数组中的重复项Ⅱ

不妨考虑更一般的情况:我们将原问题的「保留 2 位」修改为「保留 k 位」。通过移动快指针 fast 来找到新的元素,然后将新元素复制到慢指针 slow 的位置,从而在原地删除重复项。当找到一个与 nums[slow - k] 不同的元素时,我们就将新元素复制到 nums[slow]。

class Solution {
    public int removeDuplicates(int[] nums) {
        int k=2;//本体k=2,可以推广到任意k
        if(nums.length<=k) return nums.length;

        int slow=k, fast=k;
        while(fast<nums.length){
            //如果nums[fast]不等于nums[slow-k]
            //则将nums[fast]复制到nums[slow]并将slow向前移动一位
            if(nums[fast]!=nums[slow-k]){//nums[slow-k]是当前考虑的元素再新数组中的第一个可能的位置
                nums[slow]=nums[fast];
                slow++;
            }
            //将fast向前移动一位,以检查下一个元素
            fast++;
        }
        return slow;
    }
}

看起来和之前的代码逻辑有些不同,可以稍微修改一下,等效为如下代码:

class Solution {
    public int removeDuplicates(int[] nums) {
        int k=2;
        int slow=k;
        for(int fast=k;fast<nums.length;fast++){
            if(nums[fast]!=nums[slow-k]){
                nums[slow]=nums[fast];
                slow++;
            }
        }
        return slow;
    }
}

这里的关键是nums[slow-k]

169.多数元素

本题是典型的使用map的题目,不难,但记住遍历map的方式以及在map中读、添加数据的方式。

189.轮转数组

时间复杂度:O(kn)
进阶解法暂不打算细学了。

121.买卖股票的最佳时机(贪心)

从左到右枚举卖出价格 prices[i]\textit{prices}[i]prices[i],那么要想获得最大利润,我们需要知道第 iii 天之前,股票价格的最小值是什么,也就是从 prices[0]\textit{prices}[0]prices[0] 到 prices[i−1]\textit{prices}[i-1]prices[i−1] 的最小值,把它作为买入价格,这可以用一个变量 minPrice\textit{minPrice}minPrice 维护。

请注意,minPrice\textit{minPrice}minPrice 维护的是 prices[i]\textit{prices}[i]prices[i] 左侧元素的最小值。

由于只能买卖一次,所以在遍历中,维护 prices[i]−minPrice\textit{prices}[i]-\textit{minPrice}prices[i]−minPrice 的最大值,就是答案。
时间复杂度:O(n)

122.买卖股票的最佳时机

在这里插入图片描述
思路就是假设就每天买入第二天卖出,看看这样的利润是怎么样的,然后把所有赚钱的方式都加起来就是最多利润了。
可能有人就会问了,第一天买入,第二天卖出这样一定是最多利润的吗?其实这里可以想到,最多利润是可以被拆分的。比如最多利润的方式是第一天买入,第3天卖出,那么利润就是prices[2]-prices[0]。但是这个量是可以拆分的,即等于prices[2]-prices[1]+prices[1]-prices[0],也就是相当于把两天的利润加起来。因此,如果我把每天买入第二天卖出的方式都计算出来,那么如果是亏的就放弃,赚的就加入,这样就一定是最大的了,比如连着两天赚钱把赚的钱加起来和第一天买第三天卖赚的钱得到的结果是一致的。
时间复杂度:O(n)
代码如下:

class Solution {
    public int maxProfit(int[] prices) {
        int[] profit=new int[prices.length];
        int allProfit=0;
        for(int i=1;i<prices.length;i++){
            profit[i]=prices[i]-prices[i-1];
        }
        for(int i=1;i<prices.length;i++){
            if(profit[i]>0){
                allProfit+=profit[i];
            }
        }
        return allProfit;
    }
}

44.跳跃游戏

这道题目关键点在于:不用拘泥于每次究竟跳几步,而是看覆盖范围,覆盖范围内一定是可以跳过来的,不用管是怎么跳的。
只要保证:当前在覆盖范围内,且最大覆盖范围能够达到最后一个位置,那么就是true。
代码如下:

class Solution {
    public boolean canJump(int[] nums) {
        int maxRange=0;
        for(int i=0;i<nums.length;i++){
            if(maxRange>=i){
                maxRange=Math.max(maxRange,i+nums[i]);
            }else{
                return false;
            }
        }
        return true;
    }
}

45.跳跃游戏Ⅱ

贪心的思路是使用尽可能小的步数来获得尽可能大的覆盖范围。
首先,问题是每次具体跳到哪才是最小,代码随想录中的方法是设置当前的最大覆盖范围,然后我们一个一个走,一个一个判断他的最大覆盖范围,选择最大的最大覆盖范围即可。
代码如下:

class Solution {
    public int jump(int[] nums) {
        if(nums.length<2) return 0;
        int nowMaxRange=0;
        int nextMaxRange=0;
        int nextStep=0;
        int jumpStep=0;
        for(int i=0;i<nums.length;){
            nowMaxRange=i+nums[i];
            if(nowMaxRange>=nums.length-1) return ++jumpStep;
            for(int j=i;j<=nowMaxRange;j++){
                if(nextMaxRange<j+nums[j]){
                    nextStep=j;
                    nextMaxRange=j+nums[j];
                }
            }
            i=nextStep;
            jumpStep++;
        }
        return jumpStep;
    }
}

274.H指数

本题乍一看好像很复杂,完全看不懂。但是经过分析题意,我们意识到一个点:h指数必然存在于0~citations.length这些数中,且如果我们逐个寻找,如果出现第一个无法找到的数那么最大的h一定是这前一个数。为什么?后面半句话比较好理解,前面半句需要思考一下:假设h指数大于citations.length,比如为citations.length+1,那么说明有citations.length+1篇文章引用数大于citations.length+1,显然不可能。因此只要逐个遍历就可以了。
时间复杂度:O(n2n^2n2)
代码如下:

class Solution {
    public int hIndex(int[] citations) {
        int r=0;
        for(int i=1;i<=citations.length;i++){
            if(check(citations,i)) r=i;
            else break;
        }
        return r;
    }
    boolean check(int[] cs, int mid){
        int ans=0;
        for(int i:cs) if(i>=mid) ans++;
        return ans>=mid;
    }
}

当然,为了使时间复杂度更低,可以使用二分法。
时间复杂度:O(nlogn)
代码如下:

class Solution {
    public int hIndex(int[] citations) {
        int n=citations.length;
        int l=0,r=n;
        while(l<r){
            int mid=(l+r+1)/2;
            if(check(citations,mid)) l=mid;
            else r=mid-1;
        }
        return r;
    }
    boolean check(int[] cs, int mid){
        int ans=0;
        for(int i:cs) if(i>=mid) ans++;
        return ans>=mid;
    }
}

380.O(1)时间插入、删除和获取随即元素

对于 insert 和 remove 操作容易想到使用「哈希表」来实现 O(1)O(1)O(1) 复杂度,但对于 getRandom 操作,比较理想的情况是能够在一个数组内随机下标进行返回。
将两者结合,我们可以将哈希表设计为:以入参 val 为键,数组下标 loc 为值。

为了确保严格 O(1)O(1)O(1),我们不能「使用拒绝采样」和「在数组非结尾位置添加/删除元素」。
因此我们需要申请一个足够大的数组 nums(利用数据范围为 2∗10^5),并使用变量 idx 记录当前使用到哪一位(即下标在 [0,idx][0, idx][0,idx] 范围内均是存活值)。

对于几类操作逻辑:
insert 操作:使用哈希表判断 val 是否存在,存在的话返回 false,否则将其添加到 nums,更新 idx,同时更新哈希表;
remove 操作:使用哈希表判断 val 是否存在,不存在的话返回 false,否则从哈希表中将 val 删除,同时取出其所在 nums 的下标 loc,然后将 nums[idx] 赋值到 loc 位置,并更新 idx(含义为将原本处于 loc 位置的元素删除),同时更新原本位于 idx 位置的数在哈希表中的值为 loc(若 loc 与 idx 相等,说明删除的是最后一个元素,这一步可跳过);
getRandom 操作:由于我们人为确保了 [0,idx][0, idx][0,idx] 均为存活值,因此直接在 [0,idx+1)[0, idx + 1)[0,idx+1) 范围内进行随机即可。
代码如下:

class RandomizedSet {
    static int[] nums=new int[200010];
    Random random=new Random();
    Map<Integer, Integer> map=new HashMap<>();
    int idx=-1;
    public RandomizedSet() {
        
    }
    
    public boolean insert(int val) {
        if(map.containsKey(val)) return false;
        nums[++idx]=val;
        map.put(val,idx);
        return true;
    }
    
    public boolean remove(int val) {
        if(!map.containsKey(val)) return false;
        int loc=map.remove(val);
        if(loc!=idx) map.put(nums[idx],loc);
        nums[loc]=nums[idx--];
        return true;
    }
    
    public int getRandom() {
        return nums[random.nextInt(idx+1)];
    }
}

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * RandomizedSet obj = new RandomizedSet();
 * boolean param_1 = obj.insert(val);
 * boolean param_2 = obj.remove(val);
 * int param_3 = obj.getRandom();
 */

238.除自身以外数组的乘积

对于任意res[i]均由两部分组成,即
res[i]=(nums[0]×nums[1]×…×nums[i-1])×(nums[i+1]×nums[i+2]×…×nums[n-1])
=pre[i-1]×last[i+1],i=0,1,2,…,nums.length-1
因此只需要第一次遍历计算pre数组,第二次遍历计算last数组,然后即可得到res。
时间复杂度:O(n)
代码如下:

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n=nums.length;
        int[] res=new int[n];
        int[] pre=new int[n];
        int[] last=new int[n];
        pre[0]=nums[0];
        last[n-1]=nums[n-1];
        for(int i=1;i<n;i++){
            pre[i]=pre[i-1]*nums[i];
        }
        for(int i=n-2;i>=0;i--){
            last[i]=last[i+1]*nums[i];
        }

        for(int i=0;i<n;i++){
            if(i==0) res[i]=last[1];
            else if(i==n-1) res[i]=pre[n-2];
            else res[i]=pre[i-1]*last[i+1];
        }
        return res;
    }
}

134.加油站

首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。
每个加油站的剩余量rest[i]为gas[i] - cost[i]。
i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。
在这里插入图片描述
那么为什么一旦[0,i] 区间和为负数,起始位置就可以是i+1呢,i+1后面就不会出现更大的负数?
如果出现更大的负数,就是更新i,那么起始位置又变成新的i+1了。
那有没有可能 [0,i] 区间 选某一个作为起点,累加到 i这里 curSum是不会小于零呢?(肯定不会!这种情况只可能比如-1,2,-1,-1但是注意第一个-1就是小于0了,这样起点就是2的位置!) 如图:
在这里插入图片描述
如果 curSum<0 说明 区间和1 + 区间和2 < 0, 那么 假设从上图中的位置开始计数curSum不会小于0的话,就是 区间和2>0。
区间和1 + 区间和2 < 0 同时 区间和2>0,只能说明区间和1 < 0, 那么就会从假设的箭头初就开始从新选择其实位置了。

那么局部最优:**当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行。**全局最优:找到可以跑一圈的起始位置。

时间复杂度:O(n)

代码如下:

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        if(Arrays.stream(gas).sum()<Arrays.stream(cost).sum()) return -1;
        int[] rest=new int[gas.length];
        int cursum=0;
        int start=0;
        for(int i=0;i<gas.length;i++){
            rest[i]=gas[i]-cost[i];
            cursum+=rest[i];
            if(cursum<0){
                start=i+1;
                cursum=0;
            }
        }
        return start;
        
    }
}

13.罗马数字转整数

本题有个难处理的点,就是IV、IX等等如何判断。
这里给出一个非常巧妙的方法,那就是我们把所有的情况都用map存储起来,然后遍历的时候先优先判断两个是否在map里,在就加上,i+2,不在就判断1个,i+1。
代码如下:

class Solution {
    public int romanToInt(String s) {
        Map<String,Integer> map=new HashMap<>();
        map.put("I",1);
        map.put("IV",4);
        map.put("V",5);
        map.put("IX",9);
        map.put("X", 10);
        map.put("XL", 40);
        map.put("L", 50);
        map.put("XC", 90);
        map.put("C", 100);
        map.put("CD", 400);
        map.put("D", 500);
        map.put("CM", 900);
        map.put("M", 1000);

        int res=0;
        for(int i=0;i<s.length();){
            if(i+1<s.length()&&map.containsKey(s.substring(i,i+2))){
                res+=map.get(s.substring(i,i+2));
                i+=2;
            }else{
                res+=map.get(s.substring(i,i+1));
                i++;
            }
        }
        return res;
    }
}

12.整数转罗马数字

思路一:硬编码

千位数字只能由 M表示;
百位数字只能由 C,CD,D 和 CM 表示;
十位数字只能由 X,XL,L 和 XC 表示;
个位数字只能由 I,IV,V 和 IX 表示。
这恰好把这 13个符号分为四组,且组与组之间没有公共的符号。因此,整数 num 的十进制表示中的每一个数字都是可以单独处理的。

class Solution {
    public String intToRoman(int num) {
        String[] thousands = {"", "M", "MM", "MMM"};
        String[] hundreds  = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
        String[] tens      = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
        String[] ones      = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};

        StringBuffer roman=new StringBuffer();
        roman.append(thousands[num/1000]);
        roman.append(hundreds[(num%1000)/100]);
        roman.append(tens[num%100/10]);
        roman.append(ones[num%10/1]);
        return roman.toString();
    }
}

58.最后一个单词的长度

挺好想的,倒过来遍历就行了。首先找到最后一个单词的结尾位置,然后不断循环找到空格或者<0即可。
代码如下

class Solution {
    public int lengthOfLastWord(String s) {
        int n=s.length();
        int res=0;
        for(int i=n-1;;i--){
            if(s.charAt(i)!=' '){
                n=i;
                break;
            }
        }
        for(int i=n;;i--){
            if(i<0||s.charAt(i)==' '){
                break;
            }
            res++;
        }
        return res;
    }
}

14.最长公共前缀

当字符串数组长度为 0 时则公共前缀为空,直接返回;
令最长公共前缀 ans 的值为第一个字符串,进行初始化;
遍历后面的字符串,依次将其与 ans 进行比较,两两找出公共前缀,最终结果即为最长公共前缀;
如果查找过程中出现了 ans 为空的情况,则公共前缀不存在直接返回;
时间复杂度:O(s),s 为所有字符串的长度之和。
代码如下

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if(strs.length == 0) 
            return "";
        String ans = strs[0];
        for(int i =1;i<strs.length;i++) {
            int j=0;
            for(;j<ans.length() && j < strs[i].length();j++) {
                if(ans.charAt(j) != strs[i].charAt(j))
                    break;
            }
            ans = ans.substring(0, j);
            if(ans.equals(""))
                return ans;
        }
        return ans;
    }
}

151.反转字符串中的单词

我们将整个字符串都反转过来,那么单词的顺序指定是倒序了,只不过单词本身也倒序了,那么再把单词反转一下,单词不就正过来了。
所以解题思路如下:
移除多余空格
将整个字符串反转
将每个单词反转

class Solution {
   /**
     * 不使用Java内置方法实现
     * <p>
     * 1.去除首尾以及中间多余空格
     * 2.反转整个字符串
     * 3.反转各个单词
     */
    public String reverseWords(String s) {
        // System.out.println("ReverseWords.reverseWords2() called with: s = [" + s + "]");
        // 1.去除首尾以及中间多余空格
        StringBuilder sb = removeSpace(s);
        // 2.反转整个字符串
        reverseString(sb, 0, sb.length() - 1);
        // 3.反转各个单词
        reverseEachWord(sb);
        return sb.toString();
    }

    private StringBuilder removeSpace(String s) {
        // System.out.println("ReverseWords.removeSpace() called with: s = [" + s + "]");
        int start = 0;
        int end = s.length() - 1;
        while (s.charAt(start) == ' ') start++;
        while (s.charAt(end) == ' ') end--;
        StringBuilder sb = new StringBuilder();
        while (start <= end) {
            char c = s.charAt(start);
            if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
                sb.append(c);
            }
            start++;
        }
        // System.out.println("ReverseWords.removeSpace returned: sb = [" + sb + "]");
        return sb;
    }

    /**
     * 反转字符串指定区间[start, end]的字符
     */
    public void reverseString(StringBuilder sb, int start, int end) {
        // System.out.println("ReverseWords.reverseString() called with: sb = [" + sb + "], start = [" + start + "], end = [" + end + "]");
        while (start < end) {
            char temp = sb.charAt(start);
            sb.setCharAt(start, sb.charAt(end));
            sb.setCharAt(end, temp);
            start++;
            end--;
        }
        // System.out.println("ReverseWords.reverseString returned: sb = [" + sb + "]");
    }

    private void reverseEachWord(StringBuilder sb) {
        int start = 0;
        int end = 1;
        int n = sb.length();
        while (start < n) {
            while (end < n && sb.charAt(end) != ' ') {
                end++;
            }
            reverseString(sb, start, end - 1);
            start = end + 1;
            end = start + 1;
        }
    }
}

6.Z字形变换

整体的思路是遍历字符串,遍历过程中将每行都看成新的字符串构成字符串数组,最后再将该数组拼接起来即可:
在这里插入图片描述
整个字符串需要经历,向下向右,向下向右,这样的反复循环过程,设定 downdowndown 变量表示是否向下,loclocloc 变量表示当前字符串数组的下标
如果 downdowndown 为 truetruetrue,则 loc+=1loc+=1loc+=1,字符串数组下标向后移动,将当前字符加入当前字符串中
如果 downdowndown 为 falsefalsefalse,则表示向右,则 loc−=1loc-=1loc−=1,字符串数组下标向前移动,将当前字符加入当前字符串中
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

时间复杂度:O(n)O(n)O(n),nnn为字符串s的长度

代码如下:

class Solution {
    public String convert(String s, int numRows) {
        if(numRows==1) return s;

        int len=Math.min(s.length(),numRows);
        String[] rows=new String[len];
        for(int i=0;i<len;i++) rows[i]="";
        int loc=0;
        boolean down=false;

        for(int i=0;i<s.length();i++){
            rows[loc]+=s.substring(i,i+1);
            if(loc==0||loc==numRows-1) down=!down;
            loc+=down?1:-1;
        }

        String ans="";
        for(String row:rows) ans+=row;

        return ans;
    }
}

28.找出字符串中第一个匹配项的下标

本题使用KMP算法

125.验证回文串

本题显而易见使用双指针,但是不同的是需要先去掉非字母或者数字的部分(Character.isLetterOrDigit),并且用小写比较(s.toLowerCase)。
时间复杂度:O(n)
代码如下:

class Solution {
    public boolean isPalindrome(String s) {
        s=s.toLowerCase();
        String sb=new String();
        for(int i=0;i<s.length();i++){
            if((s.charAt(i)>='a'&&s.charAt(i)<='z')||(s.charAt(i)>='0'&&s.charAt(i)<='9')){
                sb=sb+s.substring(i,i+1);
            }
        }
        int i=0;
        int j=sb.length()-1;
        while(i<j){
            if(sb.charAt(i)!=sb.charAt(j)) return false;
            i++;j--;
        }
        return true;
    }
}

392.判断子序列

好像是动态规划多重背包问题和贪心相关。
不过完全可以使用双指针之类的解决,时间复杂度:O(n)

167.两数之和II

本题显然使用双指针,不难。
时间复杂度:O(n)
代码如下

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int i=0;
        int j=numbers.length-1;
        int[] res=new int[2];
        while(i<j){
            if(numbers[i]+numbers[j]==target){
                res[0]=i+1;
                res[1]=j+1;
                return res;
            }else if(numbers[i]+numbers[j]>target){
                j--;
            }else{
                i++;
            }
        }
        return res;
    }
}

11.盛最多水的容器

设两指针 i , j,指向的水槽板高度分别为 h[i] , h[j] ,此状态下水槽面积为 S(i,j) 。由于可容纳水的高度由两板中的 短板 决定,因此可得如下 面积公式 :
S(i,j)=min(h[i],h[j])×(j−i)
在每个状态下,无论长板或短板向中间收窄一格,都会导致水槽 底边宽度 −1-1−1​ 变短:

若向内 移动短板 ,水槽的短板 min(h[i],h[j]) 可能变大,因此下个水槽的面积 可能增大 。
若向内 移动长板 ,水槽的短板 min(h[i],h[j]) 不变或变小,因此下个水槽的面积 一定变小 。
因此,初始化双指针分列水槽左右两端,循环每轮将短板向内移动一格,并更新面积最大值,直到两指针相遇时跳出;即可获得最大面积。

直觉告诉我们,应该移动对应数字较小的那个指针(即此时的左指针)。这是因为,由于容纳的水量是由
两个指针指向的数字中较小值∗指针之间的距离
决定的。如果我们移动数字较大的那个指针,那么前者「两个指针指向的数字中较小值」不会增加,后者「指针之间的距离」会减小,那么这个乘积会减小。因此,我们移动数字较大的那个指针是不合理的。因此,我们移动 数字较小的那个指针。

15.三数之和

本题思路很容易想到,先给数组排序,然后固定一直位置,利用双指针遍历剩下两个位置。
难点在于如何去重,方法很多,一般使用while直到找到不一样的再进行判断。
时间复杂度:O(n2),n为数组长度
代码如下:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res=new ArrayList<>();
        if(nums == null || nums.length < 3) return res;
        Arrays.sort(nums);
        for(int i=0;i<nums.length;){
            int start=i+1;int end=nums.length-1;
            while(start<end){
                if(nums[i]+nums[start]+nums[end]==0){
                    List<Integer> path=new ArrayList<>();
                    path.add(nums[i]);path.add(nums[start]);path.add(nums[end]);
                    res.add(path);
                    while(start<end&&nums[start]==nums[start+1]) start++;
                    while(start<end&&nums[end]==nums[end-1]) end--;
                    start++;
                    end--;
                }else if(nums[i]+nums[start]+nums[end]>0) end--;
                else start++;
            }
            int now=nums[i];
            while(i<nums.length&&nums[i]==now) i++;
        }
        return res;
    }
}

209.长度最小的子数组

本题注意要找的是最小的子数组,也就是连续的索引组成的数组能够满足目标和。
首先我们思考一种最容易得逻辑,子数组的要素包括左边和右边,因此首先左边固定,然后右边不断增加,当出现当前和大于等于target时,说明右边再加也没有意义了,这就是当前左边的满足要求的子数组。然后左边每个遍历原数组都执行上述操作,并同时比较子数组长度得到最小值。
时间复杂度:O(n2)
代码如下:

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left=0;
        int res=Integer.MAX_VALUE;
        for(;left<nums.length;left++){
            int sum=0;
            for(int right=left;right<nums.length;right++){
                sum+=nums[right];
                if(sum>=target){
                    res=Math.min(res,right-left+1);
                    break;
                }
            }
        }
        return res==Integer.MAX_VALUE?0:res;
    }
}

显然是超时的。那么我们就要思考了,当找到当前sum大于等于target时候,是否应该停止本次循环呢?事实上很容易想到此时最好的做法就是right不变,left右移,因为这样做会让sum减少,有可能用更短的长度满足要求。当然也有可能不满足要求,这时候就基于让right右移就好了。
上述思路就是滑动窗口法,可以将从left到right视为一个窗口。
窗口就是 满足其和 ≥ target 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于target了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
示例图如下
在这里插入图片描述
代码如下:

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0;
        int sum = 0;
        int result = Integer.MAX_VALUE;
        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];
            while (sum >= target) {
                result = Math.min(result, right - left + 1);
                sum -= nums[left];
                left++;
            }
        }
        return result == Integer.MAX_VALUE ? 0 : result;
    }
}

3.无重复字符串的最长字串

使用双指针法,比较容易想到。
判断是否有重复可以想到使用HashMap来存,然后判断是否包含。

36.有效的数独

比较容易想到的思路就是受用HashMap存储,然后判断。
这里注意比较难的是每个子块的下标怎么判断,idx=⌊i/3⌋∗3+⌊j/3⌋
代码为:

class Solution {
    public boolean isValidSudoku(char[][] board) {
        Map<Integer, Set<Integer>> row  = new HashMap<>(), col = new HashMap<>(), area = new HashMap<>();
        for (int i = 0; i < 9; i++) {
            row.put(i, new HashSet<>());
            col.put(i, new HashSet<>());
            area.put(i, new HashSet<>());
        }
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                char c = board[i][j];
                if (c == '.') continue;
                int u = c - '0';
                int idx = i / 3 * 3 + j / 3;
                if (row.get(i).contains(u) || col.get(j).contains(u) || area.get(idx).contains(u)) return false;
                row.get(i).add(u);
                col.get(j).add(u);
                area.get(idx).add(u);
            }
        }
        return true;
    }
}

54.螺旋矩阵

本题很明显应该模拟螺旋矩阵的过程。可以发现,顺时针打印矩阵的顺序是 “从左向右、从上向下、从右向左、从下向上” 循环。
因此我们的代码可以这样考虑,每次循环执行以此“从左向右、从上向下、从右向左、从下向上” ,并且每次从。。。到。。。的操作以后都进行一次判断。过程可以参考如下:
打印方向 1. 根据边界打印 2. 边界向内收缩 3. 是否打印完毕
从左向右 左边界l ,右边界 r 上边界 t 加 111 是否 t > b
从上向下 上边界 t ,下边界b 右边界 r 减 111 是否 l > r
从右向左 右边界 r ,左边界l 下边界 b 减 111 是否 t > b
从下向上 下边界 b ,上边界t 左边界 l 加 111 是否 l > r

在这里插入图片描述
时间复杂度 O(MN): M,N 分别为矩阵行数和列数。
代码如下:

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        if(matrix.length==0) return new ArrayList<Integer>();
        int l=0,r=matrix[0].length-1,t=0,b=matrix.length-1,x=0;
        Integer[] res=new Integer[(r+1)*(b+1)];
        while(true){
            for(int i=l;i<=r;i++){//从左向右
                res[x]=matrix[t][i];
                x++;
            }
            t++;//从左向右执行完毕后,上界下移
            if(t>b) break;//遍历完毕条件:上界比下界低

            for(int i=t;i<=b;i++){//从上向下
                res[x]=matrix[i][r];
                x++;
            }
            r--;
            if(r<l) break;

            for(int i=r;i>=l;i--){//从右往左
                res[x]=matrix[b][i];
                x++;
            }
            b--;
            if(b<t) break;

            for(int i=b;i>=t;i--){//从下往上
                res[x]=matrix[i][l];
                x++;
            }
            l++;
            if(l>r) break;
        }
        return Arrays.asList(res);
    }
}

48.旋转图像

思路一:使用辅助数组
这个寻找规律就好了:
对于矩阵中第 i 行的第 j 个元素,在旋转后,它出现在倒数第 i 列的第 j 个位置。

对于矩阵中的元素 matrix[i][j],在旋转后,它的新位置为 matrix[j][n-i-1]
代码如下:

class Solution {
    public void rotate(int[][] matrix) {
        int n=matrix.length;
        //深拷贝
        int[][] tmp=new int[n][];
        for(int i=0;i<n;i++){
            tmp[i]=matrix[i].clone();
        }
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                matrix[j][n-1-i]=tmp[i][j];
            }
        }
    }
}

思路二:先镜像,后沿着对角线对折

73.矩阵置零

思考一下,我们把为0的元素的位置储存下来。比如,坐标(i,j)位置的元素为0,那么矩阵第i行和第j列均设置为0。
具体地,我们首先遍历该数组一次,如果某个元素为 000,那么就将该元素所在的行和列所对应标记数组的位置置为 true\text{true}true。最后我们再次遍历该数组,用标记数组更新原数组即可。
时间复杂度:O(mn)
代码如下:

class Solution {
    public void setZeroes(int[][] matrix) {
        //思考一下,我们把为0的元素的位置储存下来。
        //比如,坐标(i,j)位置的元素为0,那么矩阵第i行和第j列均设置为0
        //首先获取0元素的位置
        boolean[] row=new boolean[matrix.length];
        boolean[] col=new boolean[matrix[0].length];
        for(int i=0;i<matrix.length;i++){
            for(int j=0;j<matrix[i].length;j++){
                if(matrix[i][j]==0){
                    row[i]=col[j]=true;
                }
            }
        }

        //然后坐标(i,j)位置的元素为0,那么矩阵第i行和第j列均设置为0
        for(int i=0;i<matrix.length;i++){
            for(int j=0;j<matrix[i].length;j++){
                if(row[i]||col[j]) matrix[i][j]=0;
            }
        }
    }
}

383.赎金信

考虑用一个数组来记录每个字母的个数,magazine每个字母出现的次数记录在该数组中。ransomNote每个字母出现的次数从数组中减去。最后判断数组是否有复数,有的话就是false。
时间复杂度:O(m+n)
代码如下:

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] count=new int[26];
        for(int i=0;i<magazine.length();i++){
            count[magazine.charAt(i)-'a']+=1;
        }
        for(int i=0;i<ransomNote.length();i++){
            count[ransomNote.charAt(i)-'a']-=1;
        }
        for(int i=0;i<26;i++){
            if(count[i]<0) return false;
        }
        return true;
    }
}

205.同构字符串

本题主要需要理解题目意思,什么叫同构字符串。s 的任意一个字符被 t 中唯一的字符对应,同时 t 的任意一个字符被 s 中唯一的字符对应。我们考虑使用两个map进行匹配。
时间复杂度:O(n)
代码如下:

class Solution {
    public boolean isIsomorphic(String s, String t) {
        if(s.length()!=t.length()) return false;
        Map<Character,Character> map1=new HashMap<>();
        for(int i=0;i<s.length();i++){
            if(map1.containsKey(s.charAt(i))){
                if(map1.get(s.charAt(i))!=t.charAt(i)) return false;
            }else{
                map1.put(s.charAt(i),t.charAt(i));
            }
        }
        Map<Character,Character> map2=new HashMap<>();
        for(int i=0;i<t.length();i++){
            if(map2.containsKey(t.charAt(i))){
                if(map2.get(t.charAt(i))!=s.charAt(i)) return false;
            }else{
                map2.put(t.charAt(i),s.charAt(i));
            }
        }
        return true;
    }
}

49.字母异位词分组

思路一:给每个词进行排序,如果异位词那一定是相同的。
创建一个map来存key为排序后的单词,value为存放List的集合
每次得到一个key就判断map有没有,有的话就取集合加入该str,然后重新加入map。没有就新建集合加入str存入map。
时间复杂度:O(nklog⁡k),n为字符串数组长度(遍历的开销),k为最长单词长度,klogk为排序的开销。
代码如下:

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String,List<String>> map=new HashMap<>();
        for(String str:strs){
            char[] array=str.toCharArray();
            Arrays.sort(array);
            String key=new String(array);
            if(map.get(key)!=null){
                List<String> list=map.get(key);
                list.add(str);
                map.put(key,list);
            }else{
                List<String> list=new ArrayList<>();
                list.add(str);
                map.put(key,list);
            }
        }
        return new ArrayList<List<String>> (map.values());
    }
}

1.两数之和

这里必须要想到用map来存key为nums[i],value为i。
每次循环,边查边存
代码如下:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map=new HashMap<>();
        int[] res=new int[2];
        for(int i=0;i<nums.length;i++){
            if(map.containsKey(target-nums[i])){
                res[0]=i;
                res[1]=map.get(target-nums[i]);
                return res;
            }
            map.put(nums[i],i);
        }
        return null;
    }
}

时间复杂度:O(N)

202.快乐数

这里很显然不可能一直循环下去,需要考虑到循环终止的条件。如果是快乐数那么和为1即可,如果不是快乐数就应该是当出现以前出现过的数就意味着会一直循环了。因此设置一个哈希表存储出现过的数,一旦出现重复,就终止。
代码如下:

class Solution {
    public boolean isHappy(int n) {
        Set<Integer> set=new HashSet<>();
        int next=n;
        while(next!=1){
            set.add(next);
            next=happy(next);
            if(set.contains(next)) return false;
        }
        return true;
    }
    private int happy(int n){
        int res=0;
        while(n!=0){
            res+=(n%10)*(n%10);
            n=n/10;
        }
        return res;
    }
}

时间复杂度:O(log⁡n)

219.存在重复元素II

显然,哈希表存出现过的键值对,然后如果存在就比较,不存在就加入。
代码如下:

class Solution {
    public boolean containsNearbyDuplicate(int[] nums, int k) {
        Map<Integer,Integer> map=new HashMap<>();
        for(int i=0;i<nums.length;i++){
            if(map.containsKey(nums[i])){
                if(Math.abs(map.get(nums[i])-i)<=k) return true;
            }
            map.put(nums[i],i);
        }
        return false;
    }
}

时间复杂度:O(n)

128.最长连续序列

本题想要找到连续的数的个数,那么显然先排序。排序之后就想到滑动窗口啊,因为他实际上就是要找一个最大的窗口,里面的数是连续的。
但是这个思路有一个问题就是出现重复数字怎么办,可以考虑直接跳过。
代码如下:

class Solution {
    public int longestConsecutive(int[] nums) {
        if(nums.length==0) return 0;
        Arrays.sort(nums);
        int res=0;
        int cur=1;
        for(int right=1;right<nums.length;right++){
            if(nums[right]==nums[right-1]){
                continue;
            }else if(nums[right]!=nums[right-1]+1){//不连续
                res=Math.max(res,cur);
                cur=1;
            }else{
                cur++;
            }
        }
        res=Math.max(res,cur);
        return res;
    }
}
//0112

228.汇总区间

与上一题类似,依然可以使用滑动窗口的方法得到结果。
代码如下:

class Solution {
    public List<String> summaryRanges(int[] nums) {
        List<String> res=new ArrayList<>();
        if(nums.length==0) return res;
        int left=0,right=1;
        for(;right<nums.length;right++){
            if(nums[right]!=nums[right-1]+1){
                String s=right-1!=left?String.valueOf(nums[left])+"->"+String.valueOf(nums[right-1]):String.valueOf(nums[left]);
                res.add(s);
                left=right;
            }
        }
        String s=right-1!=left?String.valueOf(nums[left])+"->"+String.valueOf(nums[right-1]):String.valueOf(nums[left]);
        res.add(s);
        return res;
    }
}

56.合并区间

本题如果做过就会想到使用贪心算法。首先对数组排序,然后维护一个start和一个end,当当前区间的开始在start和end之间,就更新end为Math.max(end,intervals[i][1])。否则将当前维护的start和end作为数组存入list,并且将当前的区间作为新的start和end。
难的是这个代码挺难写的。
代码如下:

class Solution {
    public int[][] merge(int[][] intervals) {
        List<int[]> list=new ArrayList<>();
        Arrays.sort(intervals,(e1,e2)->{
            if(e1[0]==e2[0]) return e1[1]-e2[1];
            else return e1[0]-e2[0];
        });
        int start=intervals[0][0];
        int end=intervals[0][1];
        for(int i=1;i<intervals.length;i++){
            if(intervals[i][0]<=end) end=Math.max(end,intervals[i][1]);
            else{
                list.add(new int[]{start,end});
                start=intervals[i][0];
                end=intervals[i][1];
            }
        }
        list.add(new int[]{start,end});
        return list.toArray(new int[list.size()][]);
    }
}

57.插入区间

本题依然是贪心算法。维护一个范围start和end。一个一个比较,当不在当前数组范围里,就加入list。如果在则开始更新start和end为当前几个数最小的那个。结束后别忘了加入start和end构成的范围数组。并且排序一下。
代码也挺难写的。
代码如下:

class Solution {
    public int[][] insert(int[][] intervals, int[] newInterval) {
        List<int[]> list=new ArrayList<>();
        int start=Integer.MAX_VALUE;
        int end=Integer.MIN_VALUE;
        for(int i=0;i<intervals.length;i++){
            if(newInterval[0]>intervals[i][1]||newInterval[1]<intervals[i][0]) 
                list.add(new int[]{intervals[i][0],intervals[i][1]});
            else{
                start=Math.min(Math.min(start,intervals[i][0]),newInterval[0]);
                end=Math.max(Math.max(end,intervals[i][1]),newInterval[1]);
            }
        }
        if(start!=Integer.MAX_VALUE)
            list.add(new int[]{start,end});
        else
            list.add(new int[]{newInterval[0],newInterval[1]});
        int[][] res=list.toArray(new int[list.size()][]);
        Arrays.sort(res,(e1,e2)->{
            if(e1[0]==e2[0]) return e1[1]-e2[1];
            else return e1[0]-e2[0];
        });
        return res;
    }
}

452.用最少数量的箭引爆气球

使用贪心,但是排序需要注意一下。
代码如下:

class Solution {
    public int findMinArrowShots(int[][] points) {
        if(points.length<1) return 0;
        Arrays.sort(points, new Comparator<int[]>() {
            @Override
            public int compare(int[] e1, int[] e2) {
                return e1[0]<e2[0]?-1:1;
            }
        });
        
        int res=1;
        int start=points[0][0];
        int end=points[0][1];
        for(int i=1;i<points.length;i++){
            if(points[i][0]>end){
                start=points[i][0];
                end=points[i][1];
                res++;
            }else{
                start=Math.max(start,points[i][0]);
                end=Math.min(end,points[i][1]);
            }
        }
        return res;
    }
}

20.有效的括号

本题维护一个栈deque,如果是左括号就放入右括号,如果是右括号就取栈头比较是否一致,一致就继续,否则就false。
注意 ([)]这是false哦!
注意如何排除[这种情况?只要返回deque.isEmpty()就行了!因为如果是单数deque.isEmpty()一定是非空的!
代码如下:

class Solution {
    public boolean isValid(String s) {
        Deque<Character> deque=new LinkedList<>();
        for(int i=0;i<s.length();i++){
            switch(s.charAt(i)){
                case '(':
                    deque.addFirst(')');
                    break;
                case '[':
                    deque.addFirst(']');
                    break;
                case '{':
                    deque.addFirst('}');
                    break;
                default:
                    if(deque.isEmpty()||deque.removeFirst()!=s.charAt(i)) return false;
                    else continue;
            }
            
        }
        return deque.isEmpty();
    }
}

71.简化路径

本题完全想不到该怎么做。
考虑使用栈来存储每个节点。如果是"…“那么出栈,如果是”."那么不做处理,如果是其他的那么入栈。
最后比如我们的栈是从First入First出,那么最后就从Last一个一个出来就是结论了。
代码如下:

class Solution {
    public String simplifyPath(String path) {
        Deque<String> deque=new LinkedList<>();
        for(int i=0;i<path.length();){
            if(path.charAt(i)=='/'){
                i++;
                continue;//不做处理
            }
            int j=i+1;
            while(j<path.length()&&path.charAt(j)!='/') j++;
            String item=path.substring(i,j);
            if("..".equals(item)){
                if(!deque.isEmpty()) deque.removeFirst();
            }else if(!".".equals(item)){
                deque.addFirst(item);
            }
            i=j;
        }
        StringBuilder sb=new StringBuilder();
        while(!deque.isEmpty()) sb.append("/"+deque.removeLast());
        return sb.length()==0?"/":sb.toString();
    }
}

155.最小栈

本题可以取巧,两个栈一个存数据,一个只存最小的。
代码如下:

class MinStack {
    Deque<Integer> xStack;
    Deque<Integer> minStack;
    public MinStack() {
        xStack = new LinkedList<Integer>();
        minStack = new LinkedList<Integer>();
        minStack.push(Integer.MAX_VALUE);
    }
    
    public void push(int val) {
        xStack.push(val);
        minStack.push(Math.min(minStack.peek(), val));
    }
    
    public void pop() {
        xStack.pop();
        minStack.pop();
    }
    
    public int top() {
        return xStack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

150.逆波兰表达式求值

经典老题了。维护一个栈,然后如果是加减乘除就出栈前两个进行运算放回栈。如果是数字则入栈。最后输出栈顶就行。
代码如下:

class Solution {
    public int evalRPN(String[] tokens) {
        Deque<Integer> deque=new LinkedList<>();
        for(int i=0;i<tokens.length;i++){
            if("+".equals(tokens[i])||"-".equals(tokens[i])||"*".equals(tokens[i])||"/".equals(tokens[i])){
                int a=deque.removeFirst();
                int b=deque.removeFirst();
                int c=0;
                switch(tokens[i]){
                case "+":
                    c=b+a;
                    break;
                case "-":
                    c=b-a;
                    break;
                case "*":
                    c=b*a;
                    break;
                case "/":
                    c=b/a;
                    break;
                }
                deque.addFirst(c);
            }else{
                deque.addFirst(Integer.parseInt(tokens[i]));
            }
        }
        return deque.removeFirst();
    }
}

141.环形链表

一个快指针一个慢指针,如果会相遇就说明有环,如果快指针为null就说明没有。
代码如下:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(fast==slow) return true;
        }
        return false;
    }
}

2.两数相加

本题没什么好说的,直接对应相加 然后维护一个进位就可以了。
代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head=new ListNode();
        ListNode res=head;
        int x=0;//标识进位
        int c1=0,c2=0,y=0;
        while(l1!=null||l2!=null){
            if(l1==null) c1=0;
            else c1=l1.val;
            if(l2==null) c2=0;
            else c2=l2.val;
            y=c1+c2+x;
            x=0;
            if(y>=10){
                x=1;
                y=y-10;
            }
            res.next=new ListNode(y);
            res=res.next;
            if(l1!=null) l1=l1.next;
            if(l2!=null) l2=l2.next;
        }
        if(x==1) res.next=new ListNode(1);
        return head.next;
    }
}

21.合并两个有序链表

本题思路其实很简单,一个一个比较,小的就是下一个节点。
但是代码实现需要想到用递归,不然不好写。
递归三部曲:
1.终止条件:如果list1为空,就返回list2,如果list2为空,就返回list1.
2.递归函数参数,显然就是list1,list2
3.递归体,比较list1和list2的值,小的就作为下一节点,并且调用递归函数获取再下一节点。
代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1==null) return list2;
        if(list2==null) return list1;
        if(list1.val<list2.val){
            list1.next=mergeTwoLists(list1.next,list2);
            return list1;
        }else{
            list2.next=mergeTwoLists(list1,list2.next);
            return list2;
        }
    }
}

138.随机链表的复制

本题看起来很复杂,其实很简单,就是要复制一个链表,只不过链表的每个节点多了一个random属性。我们可以先复制出一个链表,并且维护一个map保存random的映射。然后再给新复制的链表逐个赋值。
代码如下:

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    public Node copyRandomList(Node head) {
        Node t=head;
        Node dummy=new Node(-10010),cur=dummy;
        Map<Node,Node> map=new HashMap<>();
        while(head!=null){
            Node node=new Node(head.val);
            map.put(head,node);
            cur.next=node;
            cur=cur.next;
            head=head.next;
        }
        cur=dummy.next;
        head=t;
        while(head!=null){
            cur.random=map.get(head.random);
            cur=cur.next;
            head=head.next;
        }
        return dummy.next;
    }
}

92.反转链表II

本题又是一个没看懂的题,他其实是要将链表从left到right这段子链表反转一下再接回去。
所以思路很明确了,首先找到left指向的节点和她前一个节点,left节点设置后一个为right后一个,然后left前一个的后一个走一个函数,该函数的作用是将链表反转并返回头节点的功能。
代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode dummy=new ListNode(0);
        dummy.next=head;
        ListNode le=head;
        ListNode re=head;
        for(int i=1;i<left;i++){
            le=le.next;
        }
        for(int i=1;i<right;i++){
            re=re.next;
        }

        ListNode pre=dummy;
        ListNode next=re.next;
        while(pre.next!=le){
            pre=pre.next;
        }
        re.next=null;
        pre.next=reverse(le);
        le.next=next;
        return dummy.next;
    }
    private ListNode reverse(ListNode head){
        ListNode temp=null;
        ListNode cur=head;
        while(cur!=null){
            ListNode next=cur.next;
            cur.next=temp;
            temp=cur;
            cur=next;
        }
        return temp;
    }
}

19.删除链表的倒数第N哥节点

本题的关键就是找出待删除结点前一个节点。
有很多方法啊,这里给出一种:先用fast向后n次,然后slow和fast一起动,最后fast到结尾,slow正好是前一个。
代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy=new ListNode(0);
        dummy.next=head;
        ListNode fast=dummy;
        ListNode slow=dummy;
        for(;n>0;n--){
            fast=fast.next;
        }
        while(fast.next!=null){
            slow=slow.next;
            fast=fast.next;
        }//此时,slow是待删除节点的前一个
        slow.next=slow.next.next;
        return dummy.next;
    }
}

82.删除排序链表中重复元素 ***

本题思路很简单,如果遇到重复,就不断往后直到找到最后一个重复的点,当前节点的后一个节点修改为这个最后一个重复的点。比较难的是代码的成功ac。
代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) {
            return head;
        }
        ListNode dummy=new ListNode(0);
        dummy.next=head;
        ListNode cur=dummy;
        while(cur.next!=null&&cur.next.next!=null){
            if(cur.next.val==cur.next.next.val){
                int x=cur.next.val;
                while(cur.next!=null&&cur.next.val==x){
                    cur.next=cur.next.next;
                }
            }else{
                cur=cur.next;
            }
        }
        return dummy.next;
    }
}

61.旋转链表

思路也很简单,先把链表变成环,然后再找到新的头节点把环断开。
难的是代码:

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if (k == 0 || head == null || head.next == null) {
            return head;
        }
        int n = 1;
        ListNode iter = head;
        while (iter.next != null) {
            iter = iter.next;
            n++;
        }
        int add = n - k % n;
        if (add == n) {
            return head;
        }
        iter.next = head;
        while (add-- > 0) {
            iter = iter.next;
        }
        ListNode ret = iter.next;
        iter.next = null;
        return ret;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值