leetCode刷题记录0

每日一题

1630. 等差子数组

1630. 等差子数组

  • 先直接暴力,过了再说
public List<Boolean> checkArithmeticSubarrays(int[] nums, int[] l, int[] r) {
        ArrayList<Boolean> ans = new ArrayList<>();
        for(int i=0;i<l.length;i++){
            int[] arrs = Arrays.copyOfRange(nums, l[i], r[i]+1);
            Arrays.sort(arrs);
            int j=1;
            while (arrs.length>1&&j<arrs.length){
                if (arrs[j] - arrs[j -1] != arrs[1] - arrs[0]) {
                    ans.add(false);
                    break;
                }
                j++;
            }
            if(j==arrs.length) ans.add(true);
        }
        return ans;
    }
  • 再追求效率

高效率的做法
一次遍历找到最大最小值 那么公差 d = (max-min)/(len-1) 不能整除直接return false
然后再遍历,第i项 a[i] = a[0]+id 不如就算出来i=(a[i]-a[0])/d 若不能整除false 或者出现重复i false Hash[d]记录是否1~d都出现过 真是等差一定会0~len-1各出现一次的
哇:两次遍历2
O(n)+一个O(n)空间的数组就OK了,完全不需要排序

public List<Boolean> checkArithmeticSubarrays(int[] nums, int[] l, int[] r) {
        ArrayList<Boolean> ans = new ArrayList<>();
        int[] Hash = new int[nums.length];
        for (int i = 0; i < l.length; i++) {
            Arrays.fill(Hash, 0);
            int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
            for (int j = l[i]; j <= r[i]; j++) {
                min = Math.min(min, nums[j]);
                max = Math.max(max, nums[j]);
            }
            if ((max - min) % (r[i] - l[i]) != 0) {
                ans.add(false);
            } else if (max == min) {//所有数字都一样
                ans.add(true);
            } else {
                int d = (max - min) / (r[i] - l[i]);
                boolean flag = true;
                for (int j = l[i]; j <= r[i]; j++) {
                    if((nums[j]-min)%d!=0) {//注意这里也得判断
                        flag = false;
                        break;
                    }
                    int t = (nums[j] - min) / d;
                    if (Hash[t] == 1) {//等差数列一定每个都一份的 否则一定会有重复
                        flag = false;
                        break;
                    } else Hash[t] = 1;
                }
                ans.add(flag);
            }
        }

        return ans;
    }

hot100题

1. 两数之和

1. 两数之和

public int[] twoSum(int[] nums, int target) {
    HashMap<Integer, Integer> map = new HashMap<>();//存放元素下表
    for (int i = 0; i < nums.length; i++) {
        if(map.containsKey(target-nums[i])){//map.containsKey()时间复杂度是O(1) !!放心大胆用
            return new int[]{i,map.get(target-nums[i])};
        }
        map.put(nums[i],i);//记录下标
    }
    return null;
}

知识点:java map.containsKey()时间复杂度是O(1), 访问大胆用即可
用map做Hash,简直不要太爽

2. 两数相加

2. 两数相加

节点个数100,一眼大数,自己再写一次大数吧。确实很简单的大数

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    ListNode zero = new ListNode(0);
    ListNode head = new ListNode(-1);//头结点使得操作统一
    ListNode tail = head;
    int carry = 0;
    while (l1 != null || l2 != null) {
        if (l1 == null) l1 = zero;
        if (l2 == null) l2 = zero;
        //System.out.println(l1.val+" "+l2.val);
        tail.next = new ListNode((l1.val + l2.val + carry) % 10);
        tail = tail.next;
        carry = (l1.val + l2.val + carry) / 10;

        l1 = l1.next;
        l2 = l2.next;
    }
    if (carry!=0){
        tail.next = new ListNode(carry);
    }
    return head.next;
}

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

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

剑指里好像刷过这题,好像就是dp+Hash, 直接上

 public int lengthOfLongestSubstring(String s) {
     int max = 0;
     int l = 0,r = 0;//首尾双指针 多好 (子串一定是连续的呀)
     int[] Hash = new int[128];//ASCII只有128个
     char[] chars = s.toCharArray();//习惯用数组
     for (r = 0; r < chars.length; r++) {
         while (Hash[chars[r]]>0){
             Hash[chars[l++]]--;
         }
         Hash[chars[r]]++;
         max = Math.max(max,r-l+1);
     }
     return max;
 }

果然是做过,一点难度没有觉察到
但是看题解发现了java的HashSet专门用来判重
(HashSet底层就是HashMap,也就是专门的Hash表去了下重复,所以contains方法也是O(1) 直接看源码就map.containsKey(o))

public int lengthOfLongestSubstring(String s) {
    int max = 0;
    int l = 0,r = 0;//首尾双指针 多好 (子串一定是连续的呀)
    HashSet<Character> set = new HashSet<>();
    char[] chars = s.toCharArray();//习惯用数组
    for (r = 0; r < chars.length; r++) {
        while (set.contains(chars[r])){
            set.remove(chars[l++]);//remove应该也是O(1) Hash表嘛 都是O(1)
        }
        set.add(chars[r]);
        max = Math.max(max,r-l+1);
    }
    return max;
}

确实好用~

4. 寻找两个正序数组的中位数

4. 寻找两个正序数组的中位数

先牺牲空间归并一下,时间完全满足O(m+n),这也能过,太水了~ 还hard

public class LC4 {

    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int[] ans = new int[nums1.length + nums2.length];
        int k = 0;
        int i = 0, j = 0;
        while (i < nums1.length && j < nums2.length) {
            if(nums1[i]<nums2[j]){
                ans[k++]=nums1[i];
                i++;
            }else {
                ans[k++]=nums2[j];
                j++;
            }
        }
        while (i<nums1.length) ans[k++] = nums1[i++];
        while (j<nums2.length) ans[k++] = nums2[j++];

        /*if(k%2==1) return ans[k/2];
        return (ans[k/2]+ans[k/2-1])/2.0;*/
        //小trick
        return (ans[(k-1)/2]+ans[k/2])/2.0;
    }

    public static void main(String[] args) {
        test(new int[]{1,3},new int[]{2});
        test(new int[]{1,2},new int[]{3,4});
        test(new int[]{1,3},new int[]{2,3,4,5});//1 2 3 3 4 5
    }

    static void test(int[] A,int[] B){
        LC4 t = new LC4();
        double ans = t.findMedianSortedArrays(A, B);
        System.out.println(ans);
    }
}
  • 空间限制O(1) 才能算hard

先想到不存储,直接找到第(l1+l2)/2旁边就截止,不就将空间省下来了吗,没想到边界竟然这么麻烦
还是在用到了奇偶都可以用 (ans[(k-1)/2]+ans[k/2]) /2.0 得到中位数的trick

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int n = nums1.length + nums2.length;
        int m1 = (n - 1) / 2;
        int m2 = n / 2;
        int k = 0, ans1 = 0, ans2 = 0;
        //不管奇偶 找这个两个取平均即可
        int i1 = 0, i2 = 0;
        while (i1 < nums1.length && i2 < nums2.length) {
            if(nums1[i1] < nums2[i2]) {
                if (k == m1) {
                    ans1 = nums1[i1];
                }
                if (k == m2) {
                    ans2 = nums1[i1];
                    return (ans1 + ans2) / 2.0;
                }
                i1++;
                k++;

            } else {

                if (k == m1) {
                    ans1 = nums2[i2];
                }
                if (k == m2) {
                    ans2 = nums2[i2];
                    return (ans1 + ans2) / 2.0;
                }
                i2++;
                k++;
            }
        }

        while (i1 < nums1.length) {
            if (k == m1) {
                ans1 = nums1[i1];
            }
            if (k == m2) {
                ans2 = nums1[i1];
                return (ans1 + ans2) / 2.0;
            }
            i1++;
            k++;
        }

        while (i2 < nums2.length) {
            if (k == m1) {
                ans1 = nums2[i2];
            }
            if (k == m2) {
                ans2 = nums2[i2];
                return (ans1 + ans2) / 2.0;
            }
            i2++;
            k++;
        }
        return -1;
    }
  • 看题解 简化代码

trick:
总长度 n = l1 + l2
奇数: k/2
偶数:k/2和(k-1)/2
都遍历到下标 k/2
或者 都 k/2和(k-1)/2取平均 (奇数时二者相等 就自然统一了)

或者直接记录pre和now即可
if (n1 < l1 && nums1[n1] < nums2[n2])改成if (n1 < l1 && (n2>=l2|| nums1[n1] < nums2[n2]))
另一个越界了也是我+,小技巧,省了大代码

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int l1 = nums1.length;
        int l2 = nums2.length;
        int len = l1 + l2;
        int n1 = 0, n2 = 0;//
        int pre = 0, now = 0;//记录上次的结果

        int T = len / 2 +1 ;//找到这就够了 下标len/2,但其实是(len/2)+1多个
        while (T-- > 0) {
            pre = now;
            if (n1 < l1 && (n2>=l2|| nums1[n1] < nums2[n2])) {//nums2越界了也是我+  k2>=l2写前面短路,否则nums2[k2]会越界
                now = nums1[n1++];
            }else {
                now=nums2[n2++];//不用想 肯定走这边 (只有2种逻辑可以走)
            }
        }

        if((len&1)==1) return now;//奇数
        return (now+pre)/2.0;
    }

5. 最长回文子串

5. 最长回文子串

  • 先用最简单的dp做一下,先通过再说
public String longestPalindrome(String s) {
    char[] arr = s.toCharArray();
    int l = arr.length;
    int[][] dp = new int[l][l];

    int start=0,maxl=1;//维护一个起始下标和长度作为最终结果返回
    for (int i = 0; i < dp.length; i++) {
        dp[i][i]=1;//长度为1
        if(i>0&&arr[i]==arr[i-1]) {
            dp[i-1][i]=1;//长度为2 千万注意i<j  i-1写到前面  因为我们维护的是起始下标和长度
            start=i-1;//i-1不是i 小细节害死人
            maxl=2;
        }
    }
    for(int len =2;len<l;len++ ){//长度为3开始遍历  [i,i+len]长度为len+1
        for (int i = 0; i+len < l; i++) {
            int j = i+len;
            if(arr[i]==arr[j]) dp[i][j] = dp[i+1][j-1];
            else dp[i][j]=0;//直接就是0 不是回文子串
            if(dp[i][j]==1&&len+1>maxl){//len+1才是实际长度
                maxl=len+1;
                start=i;
            }
        }
    }
    return new String(arr,start,maxl);//直接取数组部分初始化为String
}

代码有点长,想优化一下,总感觉优化了个寂寞:

//感觉代码有点长 休整一下
public String longestPalindrome(String s) {
    char[] arr = s.toCharArray();
    int l = arr.length;
    boolean[][] dp = new boolean[l][l];
    for (int i = 0; i < dp.length; i++) dp[i][i] = true;//长度为1

    int start = 0, maxl = 1;//维护一个起始下标和长度作为最终结果返回
    for (int len = 2; len <= l; len++) {//长度真的从2开始遍历
        for (int i = 0; i + len - 1 < l; i++) {
            int j = i + len - 1;//[i,i+len-1] 长度正好是len

            if (arr[i] != arr[j]) dp[i][j] = false;//非回文子串
            else if (len == 2) dp[i][j] = true;//长度为2的边界
            else dp[i][j] = dp[i + 1][j - 1];

            if (dp[i][j] && len > maxl) {//len+1才是实际长度
                maxl = len;
                start = i;
            }
        }
    }
    //return new String(arr, start, maxl);//直接取数组部分初始化为String
    return s.substring(start, start + maxl);//参数竟然是首尾下标
}

时间O(n^2) , 空间O(n^2)

接下来就是老老实实看题解,一步一步发掘最优解了

  • 方法二:中心扩展算法

在这里插入图片描述

此法,时间还是O(n^2),但是空间变成O(1)了
注意回文中心有两种,长度为1或者长度为2
长度为1如: a => bab 也就是奇数子串
长度为2如: aa=> baab 也就是偶数子串
所以得以回文中心为下标专门写一个扩散函数

我的写法:

public String longestPalindrome(String s) {
        char[] arr = s.toCharArray();
        int l = arr.length;
        int begin = 0, maxl = 1;
        for (int i = 0; i < l - 1; i++) {
            Integer[] ok1 = expandAroundCenter(i,i,arr);
            Integer[] ok2 = expandAroundCenter(i,i+1,arr);
            if(ok1!=null&&ok1[1]-ok1[0]+1>maxl){
                maxl = ok1[1] - ok1[0]+1;
                begin = ok1[0];
            }
            if(ok2!=null&&ok2[1]-ok2[0]+1>maxl){
                maxl = ok2[1] - ok2[0]+1;
                begin = ok2[0];
            }
        }
        return s.substring(begin, begin + maxl);//参数竟然是首尾下标 当然还是左闭右开
    }

    public Integer[] expandAroundCenter(int i, int j, char[] arr) {
        int k = 0;
        while (i - k >= 0 && j + k < arr.length && arr[i-k]==arr[j+k]) k++;
        k--;
        Integer[] ans = null;
        if(k>-1) ans = new Integer[]{i-k,j+k};//返回首尾下标
        return ans;
    }

题解写法:

public String longestPalindrome(String s) {
        if (s == null || s.length() < 1) {
            return "";
        }
        int start = 0, end = 0;
        for (int i = 0; i < s.length(); i++) {
            int len1 = expandAroundCenter(s, i, i);
            int len2 = expandAroundCenter(s, i, i + 1);
            int len = Math.max(len1, len2);
            if (len > end - start) {
                //★ 奇偶奇迹般地统一了   i=1 aaaa和aaab 举例验证 是正确的
                start = i - (len - 1) / 2;
                end = i + len / 2;
            }
        }
        return s.substring(start, end + 1);
    }

    public int expandAroundCenter(String s, int left, int right) {
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            --left;
            ++right;
        }
        return right - left - 1;//(right-1)-(left+1)+1
    }

巧妙之处在于,不管结果是奇数还是长偶数长,都给你一个式子统一了:

start = i - (len - 1) / 2;
end = i + len / 2;

eg: i=1 “aaaa”
expandAroundCenter(1,1) => 3
expandAroundCenter(1,2) => 4
len取4 此时 :
start = i - (len - 1) / 2 = 1 - 3/2 = 0
end = i + len / 2 = 1+4/2 = 3
正确
eg: i=1 “aaab”
expandAroundCenter(1,1) => 3
expandAroundCenter(1,2) => 2
len取3 此时 :
start = i - (len - 1) / 2 = 1 - 2/2 = 0
end = i + len / 2 = 1+3/2 = 2
正确

理解:

start = i - (len - 1) / 2; //i左偏了 所以 (len-1)
end = i + len / 2;  //右边正常
//然后举例论证 2个不同的例子(1奇1偶)能正确 基本全局都正确了

在这里插入图片描述
时间紧迫,不看了

11. 盛最多水的容器

11. 盛最多水的容器

面积 = 两个指针指向的数字中较小值∗指针之间的距离

如果我们移动数字较大的那个指针,那么前者「两个指针指向的数字中较小值」不会增加,后者「指针之间的距离」会减小,那么这个乘积会减小。

所以在首尾指针的情况下,让短板变长是我们的目标

本题直接暴力会超时

public int maxArea(int[] height) {
    int max = 0;
    int i=0,j=height.length-1;
    while (i<j){
        max = Math.max(max,(j-i)*Math.min(height[i],height[j]));
        if(height[i]<height[j]) i++;
        else j--;
    }
    return max;
}

下面这么写,效率会高点:

public int maxArea(int[] height) {
    int max = 0;
    int i=0,j=height.length-1;
    while (i<j){
        if(height[i]<height[j]){
            max = Math.max(max,(j-i)*height[i]);
            i++;
        }else {
            max = Math.max(max,(j-i)*height[j]);
            j--;
        }
    }
    return max;
}

15. 三数之和

15. 三数之和

直接暴力,O(n3)肯定超时,就先用Hash降为O(n2) 试试。
注意陷阱,不注重顺序必然意味着即使下标不同,但是无序3元组相同,只能算同一个。
i<j<k 可以保证下标不重复出现,但是值相同避免不了,得用Set去重

public List<List<Integer>> threeSum(int[] nums) {
     // 直接用set去重试试
     Set<List<Integer>> set = new HashSet<>();

     HashMap<Integer, Integer> map = new HashMap<>();
     for (int i = 0; i < nums.length; i++) {
         map.put(nums[i],i);
     }

     for (int i = 0; i < nums.length; i++) {
         for (int j = i+1; j < nums.length; j++) {
             int x = -(nums[i]+nums[j]);
             if(map.containsKey(x)){
                 Integer k = map.get(x);
                 if(k>j){//强制按从小到大顺序即可保证不重复
                     ArrayList<Integer> list = new ArrayList<>();
                     list.add(nums[i]);list.add(nums[j]);list.add(nums[k]);
                     Collections.sort(list);//排序好去重
                     set.add(list);
                 }
             }
         }
     }

     List<List<Integer>> ans = new ArrayList<>();
     for (List<Integer> list : set) {
         ans.add(list);
     }

     return ans;
 }

看了下题解,不错,主要是节省了空间。时间还是一样的。

17. 电话号码的字母组合

17. 电话号码的字母组合

很简单的排列问题,但是循环实在不会写,难道要写8个循环,太麻烦了吧,突然想到递归,代码确实好写多了。

String[] mp = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
String digits= "";
ArrayList<String> ans = new ArrayList<>();

public List<String> letterCombinations(String digits) {
    this.digits = digits;
    if(digits==null||"".equals(digits)) return ans;
    dfs(0,"");
    return ans;
}

void dfs(int i,String s){
    if(i==digits.length()) {
        ans.add(s);
        return;
    }
    int x = digits.charAt(i)-'0';
    for (int j = 0; j < mp[x].length(); j++) {
        char c = mp[x].charAt(j);
        dfs(i+1,s + String.valueOf(c));
    }
}

答案也是递归,思路不错!

19. 删除链表的倒数第 N 个结点

19. 删除链表的倒数第 N 个结点
经典老题了,双指针最优:

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode p=head,q=head;

    while (n-- >0&&p!=null) p=p.next;
    if(p==null){
        if(n>1) return null;//n比总长度还要大
        else {//正好删除首结点
            return head.next;
        }
    }

    while (p.next!=null){
        p=p.next;
        q=q.next;
    }
    q.next=q.next.next;
    return head;
}

20. 有效的括号

20. 有效的括号

纯粹水题:

char match(char c){
    if(c==')') return '(';
    if(c==']') return '[';
    if(c=='}') return '{';
    return '*';
}

public boolean isValid(String s) {
    Stack<Character> stack = new Stack<>();
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if(c=='('||c=='['||c=='{') stack.push(c);
        else {//右括号
            if(stack.empty()||match(c)!=stack.pop()) return false;
        }
    }
    return stack.empty();//用empty比我们自己判断stack.size()==0 要快1ms
}

用empty比我们自己判断stack.size()==0 要快1ms
所以尽量用人家的API,效率比自己写的确实要高

21. 合并两个有序链表

21. 合并两个有序链表

水题

public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
    if (list1 == null) return list2;
    if (list2 == null) return list1;
    ListNode head = new ListNode(-1);
    ListNode tail = head;//头结点
    while (list1 != null || list2 != null) {
        // 注意这个判断的写法 节省了不少代码
        if (list2 == null || (list1!=null && list1.val < list2.val)) {
            tail.next = list1;
            list1 = list1.next;
            tail = tail.next;
        } else {//只有两种分支 不必多想 上面的判断写好即可
            tail.next = list2;
            list2 = list2.next;
            tail = tail.next;
        }
    }
    return head.next;//去掉头结点
}

递归看着很有意思,写写看(实际开发,肯定不能写这种难读的代码)

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;
    }
}

空间肯定不如上面啦~

22. 括号生成

22. 括号生成

本题只有一种括号,所以只要左括号数量等于右括号数,且所有前缀都满足左括号数>=右括号数,就一定是合法的,别想复杂了

清楚了这个简单的判定合法规则,接下来就可以用最简单的暴力法试试了

// 暴力法
Integer n =0;
List<String> ans = new ArrayList<String>();
public List<String> generateParenthesis(int n) {
    this.n = 2*n;
    GeneratePair("");
    return ans;
}

void GeneratePair(String pair){
    if(pair.length()==n){
        if(valid(pair)) ans.add(pair);
        return;
    }
    GeneratePair(pair+"(");
    GeneratePair(pair+")");
}

private boolean valid(String pair) {
    int balance=0;//当前前缀: 左括号数-右括号数
    for (int i = 0; i < pair.length(); i++) {
        char c = pair.charAt(i);
        if(c=='(') balance++;
        else balance--;
        if(balance<0) return false;
    }
    return balance==0;//左右括号数要一样多
}
  • 其实上面的方法,可以很容易优化
    递归过程中就可以维护左右括号数量了,左<=n, 右<=左 才去递归,大量减枝了,没有多余的递归了
// 剪枝
Integer n =0;
List<String> ans = new ArrayList<String>();
public List<String> generateParenthesis(int n) {
    this.n = n;
    GeneratePair("",0,0);
    return ans;
}

// 改进 递归时就可以判断前缀是否合法了呀
void GeneratePair(String pair,int left,int right){
    if(pair.length()==2*n){
        ans.add(pair);
        return;
    }
    if(left+1<=n) GeneratePair(pair+"(",left+1,right);
    if(right+1<=left) GeneratePair(pair+")",left,right+1);
}

23. 合并 K 个升序链表

23. 合并 K 个升序链表

  • 先多路归并试试
public class LC23 {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length == 0) return null;

        ListNode head = new ListNode(-1);
        ListNode tail = head;
        while (true){
            int min = 0;
            for (int i = 1; i < lists.length; i++) {
                if(lists[i]!=null){
                    if(lists[min]==null||lists[i].val<lists[min].val) min = i;
                }
            }
            if (lists[min]==null) break;
            tail.next = lists[min];
            tail = tail.next;
            lists[min] = lists[min].next;
        }
        return head.next;
    }

    public static void main(String[] args) {
        test("1,4,5","1,3,4","2,6");
        test("");


    }

    public static void test(String... strs){
        ListNode[] lists = new ListNode[strs.length];
        for (int i = 0; i < strs.length; i++) {
            lists[i] = ListNode.create(strs[i]);
        }
        LC23 t = new LC23();
        ListNode ans = t.mergeKLists(lists);
        if(ans==null) System.out.println("null");
        else ans.show();
    }
}
package cn.whu.leetcode.utils;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class ListNode {
    public int val;
    public ListNode next;

    public ListNode() {
    }

    public ListNode(int val) {
        this.val = val;
    }

    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }

    //根据数组初始化一个链表 当前对象为链表头
    public ListNode(int[] vals){
        this.val = vals[0];
        ListNode tail = this;
        for (int i = 1; i < vals.length; i++) {
            tail.next = new ListNode(vals[i]);
            tail = tail.next;
        }
    }

    public ListNode(Queue<Integer> vals){
        this.val = vals.poll();
        ListNode tail = this;
        while (!vals.isEmpty()){
            tail.next = new ListNode(vals.poll());
            tail = tail.next;
        }
    }

    public static ListNode create(String strs){
        if(strs==null||strs.trim().length()==0) return null;

        String[] splits = strs.split(",");
        Queue<Integer> q = new LinkedList<Integer>();
        for (String s : splits) {
            Integer i = Integer.parseInt(s);
            q.offer(i);
        }
        return new ListNode(q);
    }

    public void show(){
        ListNode p = this;
        while (p!=null){
            System.out.print(p.val+" ");
            p = p.next;
        }
        System.out.println();
    }



}

  • 找最值,用优先队列啊
public ListNode mergeKLists(ListNode[] lists) {
    ListNode head = new ListNode(-1);//加头结点 代码好写
    ListNode tail = head;
    // 优先队列 快多了
    PriorityQueue<ListNode> Q = new PriorityQueue<>(((o1, o2) -> o1.val - o2.val));
    // 头入队
    for (ListNode headNode : lists) {
        if(headNode!=null) Q.offer(headNode);
    }
    while (!Q.isEmpty()){
        ListNode node = Q.poll();
        tail.next = node;
        tail = tail.next;
        if(node.next!=null) Q.offer(node.next);//该条队列下一个入队
    }
    return head.next;
}

优先队列比我们每次都一个个地找快多了(堆和普通线性表的差别)
在这里插入图片描述

  • 分治法用归并排序的思想进行两两合并,合并次数变logN了。觉得没有优先队列好。不写了

31. 下一个排列

31. 下一个排列

  • c++不是有现成的API吗? 先直接试试
void nextPermutation(vector<int>& nums) {
    next_permutation(nums.begin(),nums.end());
}    

果然直接就通过了
那么这题的含义应该就是让你自己实现next_permutation,也就自己递归写全排列喽。又回到了算法笔记基础知识点了。

算法笔记 4.3 递归 例题 全排列与八皇后

  • 自己直接实现 next_permutation , 就是c++ STL的算法思想,支持重复

自己写全排列太麻烦了,而且数字有重复,很麻烦,还是直接实现next_permutaton吧

在这里插入图片描述

// 半小时没想出来 直接暴力 不要浪费时间 新知识点 看一遍就会了 原创太难了 刷题的意义也是学习
public void nextPermutation(int[] nums) {
    boolean flag = false;
    for (int i = nums.length - 1; i > 0; i--) {
        if(nums[i-1]<nums[i]){
            flag = true;
            // 从后往前找第一个比nums[i-1]大的 (nums[i-1]可能排在i~end中间)
            int j = nums.length-1;
            while (nums[j]<=nums[i-1]) j--; //注意==也不行
            int t = nums[j];
            nums[j]=nums[i-1];
            nums[i-1] = t;
            // i~end再逆置即可 排序代替逆置
            Arrays.sort(nums,i,nums.length);//注意下标范围左闭右开
            break;//立刻结束
        }
    }

    if(!flag){
        // 全部逆置 (排序代替逆置)
        Arrays.sort(nums);
    }
}

上面偷懒,逆置直接排序了(因为知道一定是倒序,不必排序,直接逆置即可)

整理一下,逆置也自己写,不直接用排序了

// 半小时没想出来 直接暴力 不要浪费时间 新知识点 看一遍就会了 原创太难了 刷题的意义也是学习
public void nextPermutation(int[] nums) {
    boolean flag = false;
    for (int i = nums.length - 1; i > 0; i--) {
        if(nums[i-1]<nums[i]){
            flag = true;
            // 从后往前找第一个比nums[i-1]大的 (nums[i-1]可能排在i~end中间)
            int j = nums.length-1;
            while (nums[j]<=nums[i-1]) j--; //注意==也不行
            swap(nums,i-1,j);//交换后面最小的元素
            // i~end再逆置即可 排序代替逆置
            reverse(nums,i,nums.length-1);//自己写得 下标范围就是[i,j] 左闭右闭
            break;//立刻结束
        }
    }

    if(!flag){
        // 整个序列都是逆序的 全体逆置
        reverse(nums,0,nums.length-1);
    }
}

void swap(int[] nums,int i,int j){
    int t=nums[i];
    nums[i]=nums[j];
    nums[j]=t;
}

void reverse(int[] nums,int i,int j){//逆置i~j
    while (i<j){
        swap(nums,i,j);
        i++;j--;
    }
}

在这里插入图片描述

32. 最长有效括号

32. 最长有效括号

之前做过2个hard,太打击自信了,现在遇到hard,有巧方法就用巧方法过了再说,没有巧法,那就思考5min,没思路,直接看题解了。

  • 方法1:dp, 我感觉这个状态转移方程我是想不出来的,还好没有硬上
    用自己的话写一遍,再贴人家的吧:

dp[i] 表示以下标i为结尾的最长合法串长度,合法串必须包含s[i]且以其为结尾。
那么初始化 s[i]=='(' dp[i]=0
状态转移:

  1. s[i]==’(‘ dp[i]=0
  2. s[i]==')'
    2.1. s[i-1]=='(' dp[i] = dp[i-2]+2
    2.2. s[i-1]==')' 此时得 s[i-1-dp[i-1]]=='(' => dp[i] = dp[i-1-dp[i-1]-1] + dp[i-1]+2
    dp[i-1-dp[i-1]-1] 很细节 ()(()) 完全因为连续的(((( 将前面忽略了 断链了 很细节

在这里插入图片描述
在这里插入图片描述

开头填充一个字符,逻辑简单多了:

public int longestValidParentheses(String s) {
   s = " "+s;//开头填充一个用于占位
   int[] dp = new int[s.length()];
   int max = 0;
   for(int i=1;i<dp.length;i++){//注意下标从1开始 0是填充符了
       if(s.charAt(i)==')'){
           if(s.charAt(i-1)=='(') dp[i]=dp[i-2]+2;
           else{//s.charAt(i-1)==')'
               if(s.charAt(i-dp[i-1]-1)=='(') dp[i]=dp[i-1]+2+dp[i-dp[i-1]-2];
           }
       }
       max = Math.max(max,dp[i]);
   }
   return max;
}

不填充,加了很多判断,但是确实快一点

public int longestValidParentheses(String s) {
   int[] dp = new int[s.length()+1];
   int max = 0;
   for(int i=1;i<s.length();i++){// 未填充也可以从1开始 因为dp[0]=0 一定成立 一对括号最短长度为2
       if(s.charAt(i)==')'){
           if(s.charAt(i-1)=='('){
               if(i-2>=0) dp[i]=dp[i-2]+2;
               else dp[i]=2;
           }else{//s.charAt(i-1)==')'
               if(i-dp[i-1]-1>=0&&s.charAt(i-dp[i-1]-1)=='(') {
                   if(i-dp[i-1]-2>=0) dp[i]=dp[i-1]+2+dp[i-dp[i-1]-2];
                   else dp[i]=dp[i-1]+2;
               }
           }
       }
       if(max<dp[i]) max=dp[i];
   }
   return max;
}
  • 方法二:栈

思路也很牛,先明确,括号只有一种类型()。然后,入栈的是下标,不是字符

在这里插入图片描述

public int longestValidParentheses(String s) {
   int max = 0;

   Stack<Integer> st = new Stack<>();
   st.push(-1);
   for (int i = 0; i < s.length(); i++) {
       if(s.charAt(i)=='(') st.push(i);
       else {//s.charAt(i)==')'
           st.pop();
           if(!st.isEmpty()) {
               max=Math.max(max,i-st.peek());
           }else {
               st.push(i);//无法匹配 我就是最后一个未匹配的右
           }
       }
   }
   return max;
}

39. 组合总和

39. 组合总和

DFS排列:每个元素可选0次,1次以及多次

public List<List<Integer>> combinationSum(int[] candidates, int target) {
    //Arrays.sort(candidates);//注释了也能通过
    this.candidates = candidates;
    ans.clear();
    comb(0,target,new ArrayList<Integer>());
    return ans;
}

int[] candidates;
List<List<Integer>> ans = new ArrayList<>();
void comb(int k,int target,List<Integer> nums){

    if(target==0){
        ans.add(nums);
        return;
    }

    // target后判断
    if(k>=candidates.length) return;

    //不选
    comb(k+1,target,nums);

    // 选一个或者多个
    int t = target;
    List<Integer> numst = new ArrayList<Integer>(nums);
    while (t-candidates[k]>=0){
        t -= candidates[k];// 本结点可以选多次
        numst.add(candidates[k]);
        comb(k+1,t,numst);
    }
}

33. 搜索旋转排序数组

33. 搜索旋转排序数组

难点在于logN的时间复杂度

旋转之后的数据分为两个分别升序的部分,若能找到分割点(原来的a[0])那么在两部分分别进行两次二分查找,不就logN了

这个时候难点就在找分割点了,旋转次数是未知的,分割点也就未知,还得最高logN找分割点,这个时候题目转换成另外一个问题了。

153. 寻找旋转排序数组中的最小值

做完下面的153,再来做本题,就很简单了。

public int search(int[] nums, int target) {
    int min = findMin(nums),low=0,high=nums.length-1;
    if(target>nums[high]) high = min-1; //左边
    else  low = min;

    // 接下来 low和high里二分查找即可
    while (low<=high){//加等号 可能正好相遇时相等呢
        int mid = (low+high)/2;
        if(nums[mid]==target) return mid;
        else if(nums[mid]<target) low = mid+1;
        else high = mid-1;
    }
    return -1;

}

// 153. 寻找旋转排序数组中的最小值 改成返回最小值下标
public int findMin(int[] nums) {
    int low=0,high=nums.length-1;
    while (low<high){ // 区间长度为1时结束二分
        int mid = (low+high)/2;
        if(nums[mid]>nums[high]) low = mid+1; //改成+1 low右偏,一定在high相遇
        else high = mid;
        // 元素互不相同 不会出现相等的情况
    }
    return high;
}

接下来不找中点,直接用找中点 时nums[high]的性质,判断是在左半段还是右半段,其实用nums[0]也可以判断出来,就用nums[0]吧,target>nums[0] 一定左半段 target<nums[0] 一定在右半段,相等就直接找到了。 其实就是两个方法合二为一了

  • 利用性质直接二分

还是二分,但是每次判断3点

  1. nums[mid]和target 谁大 taregt小,下标变化要舍弃nums里一些偏大的元素, 否则舍弃nums里比较小的元素
  2. target在左边还是右边 =》 根据target和nums[0]大小可以判断 这会影响舍弃元素时下标变化的策略
  3. mid 在左边还是右边 =》 根据mid 和nums[0]大小可以判断 这会影响舍弃元素时下标变化的策略

合起来一共23-2 = 6 种情况 。 舍弃的两种是:

  1. nums[mid]<target<nums[0] : 也就是不会出现 nums[mid]<target,target右边时(target<nums[0]) ,mid在左边(mid>nums[0])
  2. nums[mid]>target>nums[0] : 也就是不会出现 nums[mid]>target,target左边时(target>nums[0]) ,mid在右边(mid<nums[0])

153. 寻找旋转排序数组中的最小值

153. 寻找旋转排序数组中的最小值

不会,直接看题解,真的简单啊。简单分析一下,就会发现,

在这里插入图片描述
nums[high]有一个非常美的性质,取一个元素t, 若t<nums[high],一定在右半段,若t>nums[high],则一定在左半段。 中点 mid = (low+high)/2 ,中点值 nums[mid]必然也满足这个性质。
(“给你一个元素值 互不相同 的数组 nums” 数组元素互不相同

mid在右半段,去掉其右边的:
在这里插入图片描述
mid在左半段,去掉其左边的:
在这里插入图片描述

边界就自己举2个例子,一奇一偶,分别手算看看即可。初步分析,因为high在右边小的部分,

  • 当然最简单的思路,最终只剩下两个元素时退出二分,取小的一个即可
public int findMin(int[] nums) {
    int low=0,high=nums.length-1;
    while (high-low>1){ // 区间长度为1时结束二分
        int mid = (low+high)/2;
        if(nums[mid]>nums[high]) low = mid;
        else high = mid;
        // 元素互不相同 不会出现相等的情况
    }
    return Math.min(nums[low],nums[high]); // 最后剩下两个 取一个最小的即可
}

真的要这么麻烦吗,为何要最后两个取最小,因为 mid = (low+high)/2 左偏了,最后一大一小时 会偏向小下标(大值)。而原本就有序时,左偏又是正确的。

那么让一大一小时,mid右偏不就行了,往右偏,最终相遇点一定偏小,(原本有序,肯定nums[0]相遇)

  • mid = (low+high)/2+1 右偏即可
 public int findMin(int[] nums) {
     int low=0,high=nums.length-1;
     while (low<high){ // 区间长度为1时结束二分
         int mid = (low+high)/2;
         if(nums[mid]>nums[high]) low = mid+1; //改成+1 low右偏,一定在high相遇
         else high = mid;
         // 元素互不相同 不会出现相等的情况
     }
     return nums[high];
 }

34. 在排序数组中查找元素的第一个和最后一个位置

34. 在排序数组中查找元素的第一个和最后一个位置

自己实现:lower_bound()和upper_bound()
可能有点难想,其实很简单,快快调试,会发现就那样很简单

注意下找不到要返回-1

public int[] searchRange(int[] nums, int target) {
    int l = lower_bound(nums, target);
    int r = upper_bound(nums, target);
    if(l==-1) return new int[]{-1,-1};
    return new int[]{l,r-1};
}

// 第一个 大于等于 target的
int lower_bound(int[] nums,int target){
    int l = 0,r = nums.length-1;
    boolean flag = false;
    while (l<=r){
        int mid = (l+r)/2;
        if(nums[mid]>=target) r=mid-1;
        else if(nums[mid]<target) l=mid+1;
        if(nums[mid]==target) flag=true;
    }
    if(flag)  return l;
    return -1;
}

// 第一个 大于 target的
int upper_bound(int[] nums,int target){
    int l = 0,r = nums.length-1;
    while (l<=r){
        int mid = (l+r)/2;
        if(nums[mid]>target) r=mid-1;
        else if(nums[mid]<=target) l=mid+1;
    }
    return l;
}

看了题解发现,两个方法可以合二为一,小tricks大大减少代码长度:

public int[] searchRange(int[] nums, int target) {
    int l = lower_bound(nums, target,true);
    int r = lower_bound(nums, target,false);
    if(l>=0&&l<=nums.length-1&&nums[l]==target) return new int[]{l,r-1};
    return new int[]{-1,-1};
}
// 和2为一: lower==true: 找第一个 大于等于 target的
//         lower==false: 找第一个 大于 target的
int lower_bound(int[] nums,int target,boolean lower){
    int l = 0,r = nums.length-1;
    while (l<=r){
        int mid = (l+r)/2;
        if(nums[mid]>target||(lower&&nums[mid]==target)) r=mid-1;
        else l=mid+1; //注意直接else (才会有动态逻辑 <或者<=)
    }
    return l;
}

42. 接雨水

42. 接雨水

  • 【不通过】方法一,之前可能行,现在超时了,数据量最大值一大,就会很慢
public int trap(int[] height) {
    int h = 1,ans=0;
    while (true){
        int l=-1,r=-1;
        boolean flag = false;
        for (int i = 0; i < height.length; i++) {
            if(height[i]>=h){
                flag = true;
                if(l==-1) l=i;
                else{
                    ans += (i-l-1);
                    l=i;
                }
            }
        }
        if(flag) h++;
        else break;
    }
    return ans;
}
  • 按列:

hard先放着吧,面试不大会问

46. 全排列

46. 全排列

基础题,java版本API细节还是不大熟悉

public List<List<Integer>> permute(int[] nums) {
    this.arr = new Integer[nums.length];
    permutation(nums,0);
    return ans;
}

Set<Integer> set = new HashSet<>();
List<List<Integer>> ans = new ArrayList<>();
Integer[] arr; //这里要Integer[] 不要int[] 否则一下API用不了 细节烦死人

List<List<Integer>> permutation(int[] nums, int n){//就用数组 arr[n]=nums[i]最方便 不需要remove 直接覆盖
    if(n==nums.length){
        ans.add(new ArrayList<>(Arrays.asList(arr)));//arr必须Integer[] 不能int
        return ans;
    }
    for (int i = 0; i < nums.length; i++) {
        if(!set.contains(i)){
            set.add(i);//第n个选i
            arr[n] = nums[i];
            permutation(nums,n+1);
            set.remove(i); //第n个不选i了
        }
    }
    return ans;
}

48. 旋转图像

48. 旋转图像

408考过类似的题,就很简单了,先每列逆置,再根据主对角线对称着逆置即可 (或者先每行逆置,再根据次对角线对称着逆置)

果然简单:

注意矩阵行列相等

// 数组就是指针传递 直接改数组就行了
public void rotate(int[][] matrix) {
    int n = matrix.length; //矩阵一定是矩形 行列相等
    // 每列原地逆置
    for (int j = 0; j < n; j++) {
        int l = 0, r = n - 1;
        while (l<r){
            swap(matrix,l,j,r,j);
            l++;r--;
        }
    }
    // 上下三角原地对称逆置
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            swap(matrix,i,j,j,i);
        }
    }
}

void swap(int[][] matrix, int a, int b, int x, int y) {
    int t = matrix[a][b];
    matrix[a][b] = matrix[x][y];
    matrix[x][y] = t;
}

49. 字母异位词分组

49. 字母异位词分组

太水了,直接暴力也能过:

Map<String,List<String>> mp = new HashMap<String,List<String>>();

public List<List<String>> groupAnagrams(String[] strs) {
    for (String str : strs) {
        char[] chars = str.toCharArray();
        Arrays.sort(chars);
        String key = new String(chars);
        if(mp.containsKey(key)){
            mp.get(key).add(str);
        }else {
            ArrayList<String> list = new ArrayList<>();
            list.add(str);
            mp.put(key,list);
        }
    }

    List<List<String>> ans = new ArrayList<>();
    for (String s : mp.keySet()) {
        ans.add(mp.get(s));
    }

    return ans;
}

看了题解,跟我写法一样,但是人家代码写得比我清爽多了:

map.getOrDefault(getOrDefault(key, default)如果存在key, 则返回其对应的value, 否则返回给定的默认值default)

public List<List<String>> groupAnagrams(String[] strs) {
    Map<String,List<String>> mp = new HashMap<String,List<String>>();
    for (String str : strs) {
        char[] chars = str.toCharArray();
        Arrays.sort(chars);
        String key = new String(chars);
        List<String> list = mp.getOrDefault(key, new ArrayList<String>());
        list.add(str);
        mp.put(key,list);//无则添加 有则覆盖 就是Hash表
    }
    return new ArrayList<>(mp.values());
}

53. 最大子数组和

53. 最大子数组和

简单DP,但是长时间不写,还是生疏了
核心: dp[i-1]>0 就连上,否则dp[i]=nums[i]只取自己(必须至少取一个啊) 然后到i+1时若dp[i]<0不要就是喽

public int maxSubArray(int[] nums) {
    int[] dp = new int[nums.length];
    dp[0] = nums[0];
    int max=dp[0];
    for (int i = 1; i < nums.length; i++) {
        /*if(dp[i-1]+nums[i]>nums[i]) dp[i] = dp[i-1]+nums[i];
        else dp[i]=nums[i];*/
        dp[i] = Math.max(dp[i-1]+nums[i],nums[i]); // 前面的和>0 加上 否则不加
        max=Math.max(max,dp[i]);
    }
    return max;
}

很明显可以滚动来压缩空间,因为每次dp[i]都只是需要用到dp[i-1]

public int maxSubArray(int[] nums) {
    int dp = nums[0],max=nums[0];
    for (int i = 1; i < nums.length; i++) {
        dp = Math.max(dp+nums[i],nums[i]); // 前面的和>0 加上 否则不加
        max=Math.max(max,dp);
    }
    return max;
}

55. 跳跃游戏

55. 跳跃游戏

public boolean canJump(int[] nums) {
    if(nums.length<=1) return true;
    // 能跨过0元素即可到达
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] == 0) {
            int j = i;
            while (j-- > 0) {
                if (nums[j] > i - j) break;
                else if(nums[j]==i-j&&i==nums.length-1) break;//刚好到最后一个也行
            }
            if (j < 0) return false;
        }
    }
    return true;
}
  • 代码优化

其实最后一个元素不必判断,能跨过前n-1所有的0,肯定能跨过倒数第2个,也就一定能到最后一个

然后再语法层面做一些优化

public boolean canJump(int[] nums) {
    // 能跨过0元素即可到达 (调试发现最后一个不必判断 能跨过前n-1所有的0 一定能到达最后)
    for (int i = 0; i < nums.length-1; i++) {
        if (nums[i] == 0) {
            int j = i;
            while (--j>=0 && nums[j]<=i-j);
            if (j < 0) return false;
        }
    }
    return true;
}

看题解发现一种特别简洁的做法,直接跳就行了,但是时间上慢了

直接跳就行了
能跳到nums[i]其左边的一定能到
每个元素都往最远的跳 都能跳就行

public boolean canJump(int[] nums) {
    // 其实一直跳就行了
    int k = 0;
    for (int i = 0; i < nums.length; i++) {
        if(i>k) return false;
        k = Math.max(k,nums[i]+i);//每个元素都往最远的跳 都能跳就行 能跳到nums[i]其左边的一定能到
    }
    return true;
}

56. 合并区间

56. 合并区间

先直接贪心取一下
过了,就行了吧

public int[][] merge(int[][] intervals) {
   // lambda表达式 指定按照第一维排序一级排序 第二维二级排序
   ArrayList<int[]> list = new ArrayList<>();
   Arrays.sort(intervals,(a1,a2)->a1[0]-a2[0]);
   for (int i = 0; i < intervals.length; i++) {
       int l = intervals[i][0],r=intervals[i][1];
       while (i+1<intervals.length&&r>=intervals[i+1][0]){//注意是维护的当前最大右边界>=intervals[i+1][0]
           r = Math.max(r,intervals[i+1][1]);//需要维护 不一定最后一个第二维就最大
           i++;
       }
       list.add(new int[]{l,r});
   }
   int[][] ans = new int[list.size()][2];
   for (int i = 0; i < list.size(); i++) {
       ans[i] = list.get(i);
   }

   return ans;
}

62. 不同路径

62. 不同路径

  • 方法1,直接用杨辉三角
    在这里插入图片描述
public int uniquePaths(int m, int n) {
    int[][] path = new int[m + 1][n + 1];//外围填充一层0就不用判断边界了
    for (int i = 1; i <=m ; i++) {
        for (int j = 1; j <= n; j++) {
            if(i==1&&j==1) path[i][j] = 1; //这个启始条件还是要有的
            else path[i][j] = path[i-1][j]+path[i][j-1];
        }
    }
    return path[m][n];
}

记得高中学排列组合时有个公式直接能算出来啊,去题解看看吧:

果然是有:
在这里插入图片描述
高中肯定会,读个大学,啥都忘了,唉~

总共移动(m-1)+(n-1)=m+n-2次 , 其中向下m-1次,向右n-1次,也就是m+n-2次中挑出m-1次向下,即: C m + n − 2 m − 1 C_{m+n-2}^{m-1} Cm+n2m1

或者m+n-2次中挑出n-1次向右,即: C m + n − 2 n − 1 C_{m+n-2}^{n-1} Cm+n2n1

这些细节还真的挺烦人:
1、 用long防止溢出
2、提前除以,防止溢出
3、m-1,n-1取其小者,防止溢出
4、Math.toIntExact(int) java Long->Int

public int uniquePaths(int m, int n) {
    Long x=1l;
    for(int i=0;i<Math.min(n-1,m-1);i++){//取小的一个 大的容易溢出
        x *= (m+n-2-i);
        x /= (i+1);
    }
    return Math.toIntExact(x);
}
  • 其实还可以简化

基本类型 long和int是可以直接强行转换的,包装类型不行

public int uniquePaths(int m, int n) {
    long ans = 1;
    for(int i=0;i<Math.min(m,n)-1;i++){
        ans = ans * (m+n-2-i)/(i+1);
    }
    return (int) ans;
}

64. 最小路径和

64. 最小路径和

最先想到的是D/BFS,没想到都超时了,只能想想别的方法了
仔细想想,不也只是一个杨辉三角嘛,本着 有经验的dp不算dp 试了试
竟然过了

// D/BFS都超时了 不行 只能杨辉试试了 再不行就直接看题解去了
public int minPathSum(int[][] grid) {
    int m = grid.length,n=grid[0].length;
    int[][] dp = new int[m+1][n+1];
    // 找最小 于是边界得填充上MAX_VALUE
    for(int i=0;i<=m;i++) dp[i][0] = Integer.MAX_VALUE;
    for(int j=0;j<=n;j++) dp[0][j] = Integer.MAX_VALUE;

    // 第0行和0列 都已经初始化过了 直接跳过
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            if(i==1&&j==1) dp[i][j]=grid[i-1][j-1];
            else dp[i][j] = grid[i-1][j-1] +  Math.min(dp[i-1][j],dp[i][j-1]);
        }         
    }
    return dp[m][n];
}
  • 下面试试不填充直接dp,看看时间能不能快一点
    果然可以。一个水题想复杂了
public int minPathSum(int[][] grid) {
    int m = grid.length,n=grid[0].length;
    for(int i=0;i<m;i++){
        for(int j=0;j<n;j++){
            if(i==0&&j==0) continue;
            else if(i==0&&j>0) grid[i][j] += grid[i][j-1];
            else if(j==0&&i>0) grid[i][j] += grid[i-1][j];
            else {
                grid[i][j] += Math.min(grid[i-1][j],grid[i][j-1]);
            }
        }
    }
    return grid[m-1][n-1];
}

70. 爬楼梯

70. 爬楼梯

纯水题,没什么好讲的,试试压缩空间的写法

// 题目比较友善 45 刚好没有溢出 否则就要大数了
public int climbStairs(int n) {
    if (n < 4) return n;
    int a = 2, b = 3, t = 0;
    for (int i = 4; i <= n; i++) {
        t = b;
        b = a + b;
        a = t;
    }
    return b;
}

75. 颜色分类

75. 颜色分类

常数空间的一趟扫描算法
l维护左边最后一个非0
r维护右边最后一个非2
遍历: 遇到0就与l交换
遇到2就与r交换

简单调试一下就OK了

public void sortColors(int[] nums) {
    int l=0,r=nums.length-1;
    while (nums[l]==0) l++;
    while (nums[r]==2) r--;
    for(int i=l;i<=r;i++){
        if(nums[i]==0) Swap(nums,i,l++);
        else if(nums[i]==2) Swap(nums,i,r--);
    }
}

75. 颜色分类

75. 颜色分类

先两次遍历试试 不要急
循序渐进

public void sortColors(int[] nums) {
    int l = 0;
    while (l<nums.length&&nums[l]==0) l++;
    for(int i=nums.length-1;i>l;i--){
        if(nums[i]==0) {
            Swap(nums,i,l++);
            while (l<nums.length&&nums[l]==0) l++;
        }
    }
    int r = nums.length-1;
    while (r>=0&&nums[r]==2) r--;
    for(int i=l;i<r;i++){
        if(nums[i]==2){
            Swap(nums,i,r--);
            while (r>=0&&nums[r]==2) r--;
        }
    }
}
void Swap(int[] nums, int x,int y){
    int t = nums[x];
    nums[x] = nums[y];
    nums[y] = t;
}

78. 子集

78. 子集

求集合子集,简单dfs

public List<List<Integer>> subsets(int[] nums) {
    dfs(nums,0,new ArrayList<Integer>());
    return ans;
}

List<List<Integer>> ans = new ArrayList<List<Integer>>();

void dfs(int[] nums,int n,List<Integer> tmp){

    if(n==nums.length){
        ans.add(new ArrayList<>(tmp));
        return;
    }

    // 不选第n个
    dfs(nums,n+1,tmp);
    //选第n个
    tmp.add(nums[n]);
    dfs(nums,n+1,tmp);
    tmp.remove(tmp.size()-1);//退栈时删除元素
}

看了题解又找到一种方法,直接迭代。枚举00…00~11…11 , 该位为1表示选,否则不选

注意: 1<<n 是 1向左边移动n位 也就是 0000000000000001 分别移动成

0000000000000001 
0000000000000010
0000000000000100 
0000000000001000 
...
1000000000000000
public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> ans = new ArrayList<>();
    ArrayList<Integer> t = new ArrayList<>();
    int n = nums.length;
    for (int mask = 0; mask < (1<<n); mask++) { // 2^n == 1<<n
        t.clear();
        // 0~2^n-1  每个数字的二进制对应位置1表示选nums中对应位置元素
        for(int i=0;i<n;i++){
            if((mask&(1<<i))!=0){//&1<<i 分别获取每位上的二进制 看是否为1 (注意不是 i<<1) 1分别向左移动0,1,2,...n-1位
                t.add(nums[i]);
            }
        }
        ans.add(new ArrayList<>(t));
    }
    return ans;
}

时间上似乎比dfs还要慢一点

79. 单词搜索

79. 单词搜索

先用dfs试试

写一半发现太难,突然想到可以有目的地DFS

有目的地深搜,可以,但是太慢了

public boolean exist(char[][] board, String word) {
    this.board = board;
    this.word = word;
    this.M = board.length;
    this.N = board[0].length;
    this.visted = new boolean[M][N];

    for (int i = 0; i < board.length; i++) {
        for (int j = 0; j < board[i].length; j++) {
            if (board[i][j] == word.charAt(0)) {
                for (boolean[] arrb : visted)  Arrays.fill(arrb,false);
                if (dfs(0, i, j)) return true;
            }
        }
    }
    return false;
}

char[][] board;
String word;
int M, N;
int[] dx = {0, 0, 1, -1};
int[] dy = {1, -1, 0, 0};
boolean[][] visted;

boolean judge(int x, int y, int n) {
    if (n >= word.length()) return false;
    if (x < 0 || x >= M || y < 0 || y >= N) return false;
    if(visted[x][y]) return false;
    if (word.charAt(n) != board[x][y]) return false;
    return true;
}

boolean dfs(int n, int x, int y) {
    //System.out.printf("(%d %d): %c\n",x,y,board[x][y]);
    visted[x][y] = true;
    if (n == word.length() - 1) return true;
    // 四个方向挑着走
    for (int i = 0; i < dx.length; i++) {
        if (judge(x + dx[i], y + dy[i], n + 1)) {
            if (dfs(n + 1, x + dx[i], y + dy[i])) return true;
        }
    }
    visted[x][y] = false; // 退栈时可认为没有访问
    return false;
}

答案思路和我一样,只是没用这么多全局变量而已,不管了。
java写代码,封装思想,以后尽量少写全局变量吧

94. 二叉树的中序遍历 (TreeNode工具类)

94. 二叉树的中序遍历

纯纯水题:

List<Integer> ans = new ArrayList<Integer>();
public List<Integer> inorderTraversal(TreeNode root) {
    if(root == null) return ans;
    inorderTraversal(root.left);
    ans.add(root.val);
    inorderTraversal(root.right);
    return ans;
}

收获是又写了一遍树的工具类

package cn.whu.utils;

import java.util.LinkedList;
import java.util.Queue;

public class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;

    TreeNode() {
    }

    TreeNode(int val) {
        this.val = val;
    }

    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }

    public static TreeNode newNode(Integer val) {
        if (val == null) return null;
        else return new TreeNode(val);
    }


    // 层序创建二叉树
    public static TreeNode create(Integer[] vals) {
        if(vals==null||vals.length==0) return null;
        Queue<TreeNode> q = new LinkedList<>();
        int k = 0;
        TreeNode root = new TreeNode(vals[k++]);
        q.offer(root);
        while (!q.isEmpty()) {
            TreeNode top = q.poll();
            if (k < vals.length) {
                top.left = newNode(vals[k++]);
                if (top.left != null) q.offer(top.left);
            }
            if (k < vals.length) {
                top.right = newNode(vals[k++]);
                if (top.right != null) q.offer(top.right);
            }
        }
        return root;
    }

    // 层序创建二叉树  参数为String ',' 隔开  (#,null) 等任意非法字符串都可以表示null
    public static TreeNode create(String strs) {
        if(strs==null||strs.trim().length()==0) return null;

        String[] split = strs.split(",");
        Integer[] vals = new Integer[split.length];

        for (int i = 0; i < split.length; i++) {
            try {
                Integer val = Integer.parseInt(split[i].trim());
                vals[i] = val;
            }catch (Exception e){
                vals[i] = null;
            }
        }
        return create(vals);
    }


    public void preOrder() {
        System.out.print(this.val + " ");
        if (this.left != null) this.left.preOrder();
        if (this.right != null) this.right.preOrder();
    }

    public void lpreOrder() {
        System.out.print("先序: ");
        this.preOrder();
        System.out.println();
    }

    public void inOrder() {
        if (this.left != null) this.left.inOrder();
        System.out.print(this.val + " ");
        if (this.right != null) this.right.inOrder();
    }

    public void linOrder(){
        System.out.print("中序: ");
        this.inOrder();
        System.out.println();
    }

    public void afterOrder() {
        if (this.left != null) this.left.inOrder();
        if (this.right != null) this.right.inOrder();
        System.out.print(this.val + " ");
    }

    public void lafterOrder() {
        System.out.print("后序: ");
        this.afterOrder();
        System.out.println();
    }
    
    public void levelOrder() {
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(this);
        while (!q.isEmpty()) {
            TreeNode top = q.poll();
            System.out.print(top.val + " ");
            if (top.left != null) q.offer(top.left);
            if (top.right != null) q.offer(top.right);
        }
    }

    public void llevelOrder() {
        System.out.print("层序: ");
        this.levelOrder();
        System.out.println();
    }

    public void show(){
        this.lpreOrder();
        this.linOrder();
        this.lafterOrder();
        this.llevelOrder();
    }

    public static void main(String[] args) {
        TreeNode root;

        Integer[] vals = {1, null, 2, 3};
        root = TreeNode.create(vals);
        root.show();

        System.out.println("---------------------");
        root = TreeNode.create("1, null, 2, 3");
        root.show();

    }

}

96. 不同的二叉搜索树

96. 不同的二叉搜索树

先看懂题目意思。问的是1,2,3,… n 这n个节点组成的BST有多少种,也就是中序遍历为1,2,3…n的二叉树有多少种。

在这里插入图片描述
在这里插入图片描述
看完答案真的好简单,但是自己想,太难想到了

public int numTrees(int n) {
    int[] G = new int[n + 1];
    G[0] = 1;//也是1 注意了
    G[1] = 1;
    for (int i = 2; i <= n; i++) {
        // 计算1~i总共多种BST 求每个G[i]也是一个循环
        for (int j = 1; j <=i; j++) {
            // 1~i中 以j为root的BST有多少种 依次累计就是 结点为1~i的BST总数
            G[i] += G[j - 1] * G[i - j]; // 这样从小求到大 保证了求后面时 前面的已经都有了
        }
    }
    return G[n];
}

98. 验证二叉搜索树

98. 验证二叉搜索树

第一眼看上去,这不水题吗?直接中序遍历一下不就行了吗?

过了,还真就是个水题

TreeNode pre = null;
public boolean isValidBST(TreeNode root) {
    if (root == null) return true;
    boolean left = isValidBST(root.left);
    if (pre != null && pre.val >= root.val) return false;
    pre = root;
    boolean right = isValidBST(root.right);
    return left && right;
}

101. 对称二叉树

101. 对称二叉树

又是一个水题,不过剑指里面刷过,dfs两个参数,从而同步地RNL和RLN即可

// RNL 和 RLN 同步遍历即可
public boolean isSymmetric(TreeNode root) {
    return RNL_RLN(root,root);
}
public boolean RNL_RLN(TreeNode root1,TreeNode root2){
    if(root1==null&&root2==null) return true;
    if(root1==null&&root2!=null) return false;
    if(root2==null&&root1!=null) return false;
    if(root1.val!=root2.val) return false;
    //System.out.println(root1.val+"\t"+root2.val);
    return RNL_RLN(root1.left,root2.right)&&RNL_RLN(root1.right,root2.left);
}

单纯语法层面可以优化代码:

// RNL 和 RLN 同步遍历即可
public boolean isSymmetric(TreeNode root) {
    return RNL_RLN(root,root);
}
public boolean RNL_RLN(TreeNode root1,TreeNode root2){
    if(root1==null&&root2==null) return true;
    if(root1==null||root2==null) return false;// 必然一个null 一个不为null 同为null的情况上面已经判断过了
    return root1.val==root2.val&&RNL_RLN(root1.left,root2.right)&&RNL_RLN(root1.right,root2.left);
}

102. 二叉树的层序遍历

102. 二叉树的层序遍历

水题,层序遍历而已,只不过人家要的是每一层分别打印而已,稍作修改即可

小技巧是可以直接获取队列长度 int T = q.size(); 然后一次弄T个就都是一层的

public List<List<Integer>> levelOrder(TreeNode root) {
     ArrayList<List<Integer>> ans = new ArrayList<>();
     if(root==null) return ans;
     Queue<TreeNode> q = new LinkedList<>();
     q.offer(root);
     while (!q.isEmpty()){
         // 每次把队列清完 里面的就是一层的
         // 注意 可以直接获取队列的size
         int T = q.size();
         ArrayList<Integer> list = new ArrayList<>();
         while (T-->0){
             TreeNode node = q.poll();
             list.add(node.val);
             if(node.left!=null) q.offer(node.left);
             if(node.right!=null) q.offer(node.right);
         }
         ans.add(list);//每次有新new的内存 也是必须new的内存
     }
     return ans;
 }

104. 二叉树的最大深度

104. 二叉树的最大深度

简单递归,或者说DFS,又是一个水题啊

太水了 , 就是写AVL时,求height的一个分支

public int maxDepth(TreeNode root) {
    if (root == null) return 0;
    if(root.left==null&&root.right==null) return 1;
    int h1 = maxDepth(root.left);
    int h2 = maxDepth(root.right);
    return Math.max(h1,h2)+1;
}

105. 从前序与中序遍历序列构造二叉树

105. 从前序与中序遍历序列构造二叉树

现在写这种题 也很水了
注意细节: inorder找根 得从给定的起点开始找,而不是从0开始找

public TreeNode buildTree(int[] preorder, int[] inorder) {
    return create(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
}
// [x,y]是preorder的字串下标   [a,b]是inorder的子串下标
TreeNode create(int[] preorder, int[] inorder, int x, int y, int a, int b) {
    if(x>y||a>b) return null;
    TreeNode root = new TreeNode(preorder[x]);
    int k = a;// 千万注意从给的起点开始找
    while (inorder[k]!=root.val) k++;//中序中找根  //只要是合法的就一定能找到
    root.left = create(preorder,inorder,x+1,x+(k-a),a,k-1);
    root.right = create(preorder,inorder,x+(k-a)+1,y,k+1,b);
    return root;
}

效率不高,可以给结点建立一个Hash表,以快速定位到根结点,不用每次在中序序列中找了。牺牲空间换时间的做法,很简单,这里就不写了。

114. 二叉树展开为链表

114. 二叉树展开为链表

先两趟遍历,过了再说

public void flatten(TreeNode root) {
    preOrder(root);
    TreeNode tail = null;
    for (TreeNode node : preList) {
        if(tail!=null) {
            tail.right = node;
            tail.left=null;
        }
        tail=node;
    }
}

List<TreeNode> preList = new ArrayList<TreeNode>();
void preOrder(TreeNode root){
    if(root==null) return;
    preList.add(root);
    preOrder(root.left);
    preOrder(root.right);
}
  • 深刻理解先序遍历的含义,直接一趟修改指针

将左子树插入到右子树的地方
将原来的右子树接到左子树的最右边节点
考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null

public void flatten(TreeNode root) {
     if(root==null) return;

     TreeNode right = root.right;
     TreeNode left = root.left;

     // 左子树插到右子树
     if(left!=null){
         root.right = root.left;

         // 原来的右子树不空 插到左子树 最右边
         if(right!=null){
             TreeNode node = left;
             while (node.right!=null) node=node.right;
             node.right = right;
         }
     }

     root.left=null;//左子树一定置为null
     // 继续拉伸右子树
     flatten(root.right);
 }
  • 还有一种有趣的解法

先遍历每次将前一个的right指向后一个,这样会导致原来的尚未遍历的right右子树丢失
这个时候,使用逆先序遍历,就避免这个问题了
注意先序:NLR => 逆先序:RLN

先写逆先序:

public void flatten(TreeNode root) {
    if(root==null) return;
    flatten(root.right);//注意先右
    flatten(root.left);
    System.out.println(root.val);
}
TreeNode pre = null;
public void flatten(TreeNode root) {
    if(root==null) return;
    flatten(root.right);
    flatten(root.left);
    root.left = null;
    root.right = pre;
    pre = root;
}

不得不说,逆先序遍历,太牛了~
在这里插入图片描述

121. 买卖股票的最佳时机

121. 买卖股票的最佳时机

一趟遍历,维护当前遍历到的最小值即可

public int maxProfit(int[] prices) {
    int min = prices[0];
    int max = 0;
    for (int i = 1; i < prices.length; i++) {
        int t = prices[i]-min;
        if(t>0) {
            if(t>max) max=t;
        }else {
            min = prices[i];
        }
    }
    return max;
}

128. 最长连续序列

128. 最长连续序列

用Hash表判断相邻元素是否存在
java HashSet 的contain方法就是Hash判断,时间复杂度就是O(1)

关键点:if (!hash.contains(num - 1)),每个串只会遍历一次,保证了O(n)

 // 1、看清题意 2、时间O(n)
// 思路,用Hash表O(1)判断相邻元素是否存在
public int longestConsecutive(int[] nums) {
    HashSet<Integer> hash = new HashSet<Integer>();
    for (int num : nums) {
        hash.add(num);
    }
    int max = 0;
    for (int num : nums) {
        // 我不是最好的起点 就不做起点 (这样就避免了不必要的遍历 或者说 只会遍历一次)
        if (!hash.contains(num - 1)) {
            int k = 1;
            // 我是最好的起点 可以开始查
            while (hash.contains(num + k)) k++;
            max = Math.max(max, k);
        }
    }
    return max;
}

136. 只出现一次的数字

136. 只出现一次的数字

剑指刷过 现在做就简单了 直接全部异或即可 异或就是相同取0相异取1嘛 而且有交换律和结合律 最终剩一个就是结果了

public int singleNumber(int[] nums) {
    int ans = nums[0];
    for (int i = 1; i < nums.length; i++) {
        ans ^= nums[i];
    }
    return ans;
}

139. 单词拆分

139. 单词拆分

  • 完全看着题解写的dp,看懂容易,想到难啊

个人觉得核心还是hash表判断一个元素是否存在是O(1)的时间复杂度

public boolean wordBreak(String s, List<String> wordDict) {
    HashSet<String> hash = new HashSet<>();
    for (String s1 : wordDict) {
        hash.add(s1);
    }
    boolean[] dp = new boolean[s.length()+1];
    dp[0] = true;//很重要 默认是false
    //先一定明确 dp[j]表示s[0~j-1]是否存在     check[j,i-1]表示s[j~i-1]是否存在
    for (int i = 1; i <= s.length(); i++) {//dp[0]已经初始化了 要从1开始
        for(int j=0;j<i;j++){
            dp[i] |= (dp[j]&hash.contains(s.substring(j,i)));
            if(dp[i]) break;//true了就行了
        }
    }
    return dp[s.length()];
}
  • 优化,记录一下最长长度,可以减少一些不必要的判断
// 优化 记录一下长度
public boolean wordBreak(String s, List<String> wordDict) {
    HashSet<String> hash = new HashSet<>();
    int maxl = 0;
    for (String s1 : wordDict) {
        hash.add(s1);
        maxl = Math.max(maxl,s1.length());
    }
    boolean[] dp = new boolean[s.length()+1];
    dp[0] = true;//很重要 默认是false
    //先一定明确 dp[j]表示s[0~j-1]是否存在     check[j,i-1]表示s[j~i-1]是否存在
    for (int i = 1; i <= s.length(); i++) {//dp[0]已经初始化了 要从1开始
        for(int j=0;j<i;j++){
            if(i-j>maxl) dp[i]=false;//注意长度是 (i-1)-j+1=i-j  //长度超限 直接就可以退出了
            else dp[i] |= (dp[j]&hash.contains(s.substring(j,i)));
            if(dp[i]) break;//true了就行了
        }
    }
    return dp[s.length()];
}

141. 环形链表

141. 环形链表

public boolean hasCycle(ListNode head) {
    if(head==null) return false;
    ListNode p = head,q=head;
    // 快慢指针,相遇就是有环
    while (true){
        p = p.next;
        q = q.next;
        if(q!=null) q=q.next;
        if(q==null) return false;
        if (p==q) return true;
    }
}

很简单,但是自己造环花了好长时间

public class LC141 {
    public boolean hasCycle(ListNode head) {
        if(head==null) return false;
        ListNode p = head,q=head;
        // 快慢指针,相遇就是有环
        while (true){
            p = p.next;
            q = q.next;
            if(q!=null) q=q.next;
            if(q==null) return false;
            if (p==q) return true;
        }
    }

    public static void main(String[] args) {
        test(new int[]{3,2,0,-4}, 1);
        test(new int[]{1,2}, 0);
        test(new int[]{3,2,0,-4}, -1);
        test(new int[]{}, -1);
    }

    private static void test(int[] head, int pos) {
        // 1. 创建初始列表
        ListNode root = create(head, pos);
        print(root, head);
        // 4. 测试
        LC141 t = new LC141();
        boolean b = t.hasCycle(root);
        System.out.println(b);
    }

    private static ListNode create(int[] head, int pos) {
        // 1. 创建初始列表
        ListNode root = ListNode.create(head);

        if(root==null) return root;

        // 2. 找到尾结点和环入口
        ListNode tail = root;
        ListNode circle = null;
        for(int i = 0; i< head.length; i++) {
            if(i == pos) circle = tail;//pos为负数 circle保持为null
            if(tail.next!=null) tail = tail.next;
        }

        // 3. 成环
        tail.next = circle;
        //System.out.println(tail.val+"   "+circle.val);

        return root;
    }


    static void print(ListNode root,int[] head){
        ListNode node = root;
        for(int i=0;i<head.length*3;i++){
            if(node!=null) {
                System.out.print(node.val+" ");
                node = node.next;
            }
        }
    }
}

142. 环形链表 II

142. 环形链表 II

承接上题: 多走的距离肯定是开始那段距离 (多走的距离指的是 对环长取余后 的距离) 因此让q回归起点再同步走即可

public ListNode detectCycle(ListNode head) {
    if(head==null) return null;
    ListNode p = head,q=head;
    // 快慢指针,相遇就是有环
    while (true){
        p = p.next;
        q = q.next;
        if(q!=null) q=q.next;
        if(q==null) return null;
        if (p==q){
            p = head; //多走的距离肯定是开始那段距离 (多走的距离指的是 对环长取余后 的距离) 因此让q回归起点再同步走即可
            while (q!=p){
                q=q.next;
                p=p.next;
            }
            return p;//一定再环入口处相遇
        }
    }
}

146. LRU 缓存 ★

146. LRU 缓存

写了2个多小时,总算通过了,完全自己的思路,太难了

// 思路没错 Hash表+双向链表
class ListNode {
    int value;
    int key;
    ListNode pre, next;
    ListNode head, tail;

    ListNode() {
        head = this;
        tail = head;
    }//有头结点

    ListNode(int value, int key) {
        this.value = value;
        this.key = key;
        head = this;
        tail = head;
    }

    void addFirst(ListNode node) {
        // 带头结点 头插
        node.next = head.next;
        if (head.next != null) head.next.pre = node;
        head.next = node;
        node.pre = head;
        if (tail == head) tail = node;//千万注意tail的维护
    }

    ListNode removeLast() {
        ListNode ret = tail;
        tail.pre.next = null;
        tail = tail.pre;
        return ret;
    }

    void move2First(ListNode node) {
        if(node==tail) removeLast();//千万注意tail的维护
        node.pre.next = node.next;
        if (node.next != null) node.next.pre = node.pre;
        addFirst(node);
    }
}

class LRUCache {

    //map[key]->LinkedList->当前结点值就是value

    // 帮忙维护key
    ListNode cache = new ListNode();
    int SIZE = 0;
    int len = 0;
    // Hash使得取是O(1)
    HashMap<Integer, ListNode> map = new HashMap<>();

    public LRUCache(int capacity) {
        this.SIZE = capacity;
    }

    public int get(int key) {
        if (map.containsKey(key)) {
            //移动到最前面
            cache.move2First(map.get(key));
            return map.get(key).value;
        } else return -1;
    }

    public void put(int key, int value) {
        if (map.containsKey(key)) {//直接修改后移动
            ListNode node = map.get(key);
            node.value = value;
            cache.move2First(node);
        }else if (len < SIZE){//直接头插 结点数多一个
            ListNode node = new ListNode(value,key);
            map.put(key, node);
            cache.addFirst(node);
            len++;//这里new了结点的才需要++
        }else { // 删除最后一个 再头部插入一个新的
            map.remove(cache.removeLast().key);//删除末尾 同时从hash中去掉呀
            ListNode node = new ListNode(value,key);
            map.put(key, node);
            cache.addFirst(node);
        }
    }
}

完整测试代码:

package cn.whu.leetcode.lc146_1LRU缓存;


import java.util.HashMap;

// 思路没错 Hash表+双向链表
class ListNode {
    int value;
    int key;
    ListNode pre, next;
    ListNode head, tail;

    ListNode() {
        head = this;
        tail = head;
    }//有头结点

    ListNode(int value, int key) {
        this.value = value;
        this.key = key;
        head = this;
        tail = head;
    }

    void addFirst(ListNode node) {
        // 带头结点 头插
        node.next = head.next;
        if (head.next != null) head.next.pre = node;
        head.next = node;
        node.pre = head;
        if (tail == head) tail = node;//千万注意tail的维护
    }

    ListNode removeLast() {
        ListNode ret = tail;
        tail.pre.next = null;
        tail = tail.pre;
        return ret;
    }

    void move2First(ListNode node) {
        if(node==tail) removeLast();//千万注意tail的维护
        node.pre.next = node.next;
        if (node.next != null) node.next.pre = node.pre;
        addFirst(node);
    }
}

class LRUCache {

    //map[key]->LinkedList->当前结点值就是value

    // 帮忙维护key
    ListNode cache = new ListNode();
    int SIZE = 0;
    int len = 0;
    // Hash使得取是O(1)
    HashMap<Integer, ListNode> map = new HashMap<>();

    public LRUCache(int capacity) {
        this.SIZE = capacity;
    }

    public int get(int key) {
        if (map.containsKey(key)) {
            //移动到最前面
            cache.move2First(map.get(key));
            return map.get(key).value;
        } else return -1;
    }

    public void put(int key, int value) {
        if (map.containsKey(key)) {//直接修改后移动
            ListNode node = map.get(key);
            node.value = value;
            cache.move2First(node);
        }else if (len < SIZE){//直接头插 结点数多一个
            ListNode node = new ListNode(value,key);
            map.put(key, node);
            cache.addFirst(node);
            len++;//这里new了结点的才需要++
        }else { // 删除最后一个 再头部插入一个新的
            map.remove(cache.removeLast().key);//删除末尾 同时从hash中去掉呀
            ListNode node = new ListNode(value,key);
            map.put(key, node);
            cache.addFirst(node);
        }
    }
}

public class LC146 {
    public static void main(String[] args) {

        /*String[] cmd = {"LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"};
        int[][] vals = new int[][]{{2}, {1, 1}, {2, 2}, {1}, {3, 3}, {2}, {4, 4}, {1}, {3}, {4}};
        test(cmd, vals);*/

        String[] cmd = {"LRUCache","put","put","put","put","put","get","put","get","get","put","get","put","put","put","get","put","get","get","get","get","put","put","get","get","get","put","put","get","put","get","put","get","get","get","put","put","put","get","put","get","get","put","put","get","put","put","put","put","get","put","put","get","put","put","get","put","put","put","put","put","get","put","put","get","put","get","get","get","put","get","get","put","put","put","put","get","put","put","put","put","get","get","get","put","put","put","get","put","put","put","get","put","put","put","get","get","get","put","put","put","put","get","put","put","put","put","put","put","put"};
        int[][] vals = new int[][]{{10},{10,13},{3,17},{6,11},{10,5},{9,10},{13},{2,19},{2},{3},{5,25},{8},{9,22},{5,5},{1,30},{11},{9,12},{7},{5},{8},{9},{4,30},{9,3},{9},{10},{10},{6,14},{3,1},{3},{10,11},{8},{2,14},{1},{5},{4},{11,4},{12,24},{5,18},{13},{7,23},{8},{12},{3,27},{2,12},{5},{2,9},{13,4},{8,18},{1,7},{6},{9,29},{8,21},{5},{6,30},{1,12},{10},{4,15},{7,22},{11,26},{8,17},{9,29},{5},{3,4},{11,30},{12},{4,29},{3},{9},{6},{3,4},{1},{10},{3,29},{10,28},{1,20},{11,13},{3},{3,12},{3,8},{10,9},{3,26},{8},{7},{5},{13,17},{2,27},{11,15},{12},{9,19},{2,15},{3,16},{1},{12,17},{9,1},{6,19},{4},{5},{5},{8,1},{11,7},{5,2},{9,28},{1},{2,2},{7,4},{4,22},{7,24},{9,26},{13,28},{11,26}};

        test(cmd,vals);



    }

    private static void test(String[] cmd, int[][] vals) {
        LRUCache lruCache = new LRUCache(vals[0][0]);

        for (int i = 1; i < cmd.length; i++) {
            if (cmd[i].equals("put")) {
                lruCache.put(vals[i][0], vals[i][1]);
                System.out.printf("put (%d,%d)\n",vals[i][0],vals[i][1]);
            } else {
                System.out.printf("【get %d: %d】\n",vals[i][0],lruCache.get(vals[i][0]));
            }
            print(lruCache.cache);
        }
    }

    static void print(ListNode node){
        while (node.next!=null){
            System.out.print("("+node.next.key+","+node.next.value+")");
            node=node.next;
            if(node.next!=null) System.out.print(" -> ");
        }
        System.out.println();
    }

}

现在总结一下中心思想,首先用一个双向链表存储(key-value (2个成员即可))
map(key)->定位到链表结点
每次put或者get的结点都要在头部

双向链表维护首位指针,头插、尾插、删除指定结点,时间复杂度都是O(1)

Trick:维护一个头结点,相当于伪首部
同时可以维护一个伪的tail, 伪尾部
代码会好写很多

改进后的代码如下:

根据题解优化了一下,主要就是多了一个伪首部,方便了点
然后封装了一个单独的removeNode方法

import java.util.HashMap;

// 思路没错 Hash表+双向链表
class LRUCache {
    class DLinkedNode {
        int key, value;
        DLinkedNode pre, next;
        DLinkedNode() {}
        DLinkedNode(int key, int value) {
            this.value = value;
            this.key = key;
        }
    }

    // 双向链表的伪首部和伪尾部
    DLinkedNode head, tail;
    int size, capacity;
    //map[key]->ListNode->当前结点值就是value
    HashMap<Integer, DLinkedNode> cache = new HashMap<>();// Hash使得取是O(1)

    public LRUCache(int capacity) {
        this.capacity = capacity;
        // 伪首部和伪尾部
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.pre = head;
    }

    public int get(int key) {
        if (cache.containsKey(key)) {
            move2Head(cache.get(key));//移动到最前面
            return cache.get(key).value;
        } else return -1;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node != null) {//直接修改后移动
            node.value = value;
            move2Head(node);
        } else {//肯定要创建新结点
            DLinkedNode nodei = new DLinkedNode(key, value);
            cache.put(key, nodei);
            addFirst(nodei);
            size++;

            if (size > capacity) {
                DLinkedNode tail = removeLast();
                cache.remove(tail.key);//为何要存储key 因为这里要用
                size--;
            }
        }
    }


    private DLinkedNode removeLast() {
        DLinkedNode ret = tail.pre;
        removeNode(tail.pre);
        return ret;
    }


    private void addFirst(DLinkedNode node) {
        node.next = head.next;
        head.next.pre=node;
        head.next = node;
        node.pre = head;
    }

    private void move2Head(DLinkedNode node) {
        removeNode(node);
        addFirst(node);
    }

    // 写一个辅助函数会方便许多
    private void removeNode(DLinkedNode node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

}

其实java有一个现成的数据结构直接就实现了这个:LinkedHashMap, 非常支持LRU

就是HashMap+双向链表,直接就实现了LRU,直接用他即可

LinkedHashMap里有3个特别好的方法:
1) void afterNodeAccess(Node<K,V> p) { }
只要访问了HashMap里的元素,就会将该元素放到链尾部
这里的访问是指,get或者put

    1. 使用 get 方法会访问到节点, 从而触发调用这个方法
    1. 使用 put 方法插入节点, 如果 key 存在, 也算要访问节点, 从而触发该方法
    1. 只有 accessOrder 是 true 才会调用该方法
    1. 这个方法会把访问到的最后节点重新插入到双向链表结尾

所以最近访问的一定在尾部,已经可以保证了

(删除时得从头部删除了 或者说现在容量超限时 删除头部即可达到题目要求)

2)void afterNodeInsertion(boolean evict) { }

在插入新元素之后,触发此方法,需要回调函数判断是否需要移除一直不用的某些元素!

给一个boolean条件, 这个方法会自动移除一个头部元素

3)void afterNodeRemoval(Node<K,V> p) { } (本题用不到)

在hashmap.remove删除元素之后,会触发这个方法,将元素从双向链表中删除(很容易 因为直接能拿到结点本身)

package cn.whu.leetcode.lc146_3LRU缓存;

import java.util.LinkedHashMap;
import java.util.Map;

class LRUCache extends LinkedHashMap {
    int capacity = 0;

    //初始化内置HashMap
    public LRUCache(int capacity) {
        // 第三个参数false: 按照插入顺序排序  true: 按照读取顺序排序 (读取后会重排)
        super(capacity, 0.75F, true);
        this.capacity=capacity;
    }

    // 在内置的HashMap内get
    public int get(int key) {
        return (int) super.getOrDefault(key,-1);
    }

    // 在内置的HashMap内put
    public void put(int key, int value) {
        super.put(key,value);
    }

    // get或者put之后 会触发afterNodeAccess 将刚刚访问得元素移动到内置双向链表末尾

    @Override //给 afterNodeAccess() 方法用  插入后且满足此方法条件,会删除一些旧的元素
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size()>capacity;//HashMap得size()方法 直接获取长度
    }
}

public class LC146 {
    public static void main(String[] args) {
        String[] cmd = {"LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"};
        int[][] vals = new int[][]{{2}, {1, 1}, {2, 2}, {1}, {3, 3}, {2}, {4, 4}, {1}, {3}, {4}};
        test(cmd, vals);
    }

    private static void test(String[] cmd, int[][] vals) {
        LRUCache lruCache = new LRUCache(vals[0][0]);
        for (int i = 1; i < cmd.length; i++) {
            if (cmd[i].equals("put")) {
                lruCache.put(vals[i][0], vals[i][1]);
                System.out.printf("put (%d,%d)\n", vals[i][0], vals[i][1]);
            } else {
                System.out.printf("【get %d: %d】\n", vals[i][0], lruCache.get(vals[i][0]));
            }
        }
    }
}

测试结果,和我写的效率差不了多少

不难,确实不难,还是练得不够,得多刷题呀

  • 二刷
class LRUCache {

    class Node{
        int key,value;
        Node pre,next;
        public Node(){}
        public Node(int key,int value){
            this.key = key;
            this.value = value;
        }
    }

    Node head;//头结点
    Node tail;//尾结点  均只是起一个占位作用
    int capacity = 0;

    HashMap<Integer, Node> map;


    public LRUCache(int capacity) {
        this.capacity = capacity;

        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.pre = head;

        map = new HashMap<>();
    }

    public int get(int key) {
        if(map.containsKey(key)){
            Node node = map.get(key);
            move2Head(node);
            return node.value;
        }else {
            return -1;
        }

    }

    public void put(int key, int value) {
        if(map.containsKey(key)){
            Node node = map.get(key);
            node.value = value;
            move2Head(node);
        }else {
            Node node = new Node(key, value);
            addFirst(node);
            map.put(key,node);//千万别忘了!!!
            if(map.size()>capacity){
                map.remove(tail.pre.key); //注意map里也得删除 //不要再remove方法里删除 否则move2First时也会被删除
                remove(tail.pre);//删除末尾的
            }
        }

    }

    public void remove(Node node){
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    public void addFirst(Node node){
        Node first = head.next;

        first.pre = node;
        node.next = first;

        head.next = node;
        node.pre = head;
    }

    public void move2Head(Node node){
        remove(node);
        addFirst(node);
    }

}
  • 二刷完整代码
package cn.whu.leetcode.interview150.lc146LRU缓存.二刷.es02;


import cn.whu.utils.HzaUtils;
import cn.whu.utils.Solution;

import java.util.HashMap;
import java.util.List;

class LRUCache {

    class Node{
        int key,value;
        Node pre,next;
        public Node(){}
        public Node(int key,int value){
            this.key = key;
            this.value = value;
        }
    }

    Node head;//头结点
    Node tail;//尾结点  均只是起一个占位作用
    int capacity = 0;

    HashMap<Integer, Node> map;


    public LRUCache(int capacity) {
        this.capacity = capacity;

        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.pre = head;

        map = new HashMap<>();
    }

    public int get(int key) {
        if(map.containsKey(key)){
            Node node = map.get(key);
            move2Head(node);
            return node.value;
        }else {
            return -1;
        }

    }

    public void put(int key, int value) {
        if(map.containsKey(key)){
            Node node = map.get(key);
            node.value = value;
            move2Head(node);
        }else {
            Node node = new Node(key, value);
            addFirst(node);
            map.put(key,node);//千万别忘了!!!
            if(map.size()>capacity){
                map.remove(tail.pre.key); //注意map里也得删除 //不要再remove方法里删除 否则move2First时也会被删除
                remove(tail.pre);//删除末尾的
            }
        }

    }

    public void remove(Node node){
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    public void addFirst(Node node){
        Node first = head.next;

        first.pre = node;
        node.next = first;

        head.next = node;
        node.pre = head;
    }

    public void move2Head(Node node){
        remove(node);
        addFirst(node);
    }

}


public class LC146 extends Solution {

    public static void main(String[] args) {
        String[] cmds = HzaUtils.string2Strings("[LRUCache, put, put, get, put, get, put, get, get, get]");
        List<List<String>> data = HzaUtils.string2StringList2D("[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]");

        print(cmds);
        print(data);

        LRUCache cache = null;

        for (int i = 0; i < cmds.length; i++) {
            print();
            String cmd = cmds[i];

            int a = Integer.parseInt(data.get(i).get(0));
            int b = 0;
            if(data.get(i).size()==2){
                b = Integer.parseInt(data.get(i).get(1));
            }

            if("LRUCache".equals(cmd)){
                cache = new LRUCache(a);
            }else if("put".equals(cmd)){
                cache.put(a,b);
            }else if("get".equals(cmd)){
                int ans = cache.get(a);
                print(ans);
            }
            //prints(cmd,":",data.get(i));
            //show(cache);
        }
    }

    static void show(LRUCache cache){
        LRUCache.Node node = cache.head.next;
        System.out.print("[");
        while (node!=cache.tail){
            System.out.print("["+node.key+"->"+node.value+"],");
            node = node.next;
        }
        System.out.println("]");

        System.out.print("{");
        for (Integer key : cache.map.keySet()) {
            System.out.print(key+"="+cache.map.get(key).value+", ");
        }
        System.out.println("}");
    }
}

148. 排序链表 ▲

148. 排序链表

二刷最优解(代码最优)

package cn.whu.leetcode.interviewing.lc148排序链表;

class ListNode{
    int val;
    ListNode next;
    ListNode(int val){
        this.val = val;
    }

    public static ListNode create(int[] vals){
        ListNode head = new ListNode(0);
        ListNode tail = head;

        for (int i = 0; i < vals.length; i++) {
            tail.next = new ListNode(vals[i]);
            tail = tail.next;
        }
        return head.next;
    }

    void show(){
        ListNode p = this;
        while (p!=null){
            System.out.print(p.val+" ");
            p = p.next;
        }
        System.out.println();
    }

}


public class LC148 {
    public ListNode sortList(ListNode head) {
        //head.show();
        if(head==null||head.next==null) return head;

        //先划分
        ListNode fast = head.next, slow = head;
        while (fast!=null&&fast.next!=null){
            fast = fast.next.next;
            slow = slow.next;
        }

        ListNode mid = slow.next;
        slow.next = null;
        //head.show();
        //mid.show();

        ListNode left = sortList(head);//递归二分
        ListNode right = sortList(mid);//递归二分

        // 开始回溯归并
        ListNode ans = new ListNode(-1);
        ListNode tail = ans;

        while (left!=null&&right!=null){
            if(left.val<right.val){
                tail.next = left;
                left = left.next;
            }else {
                tail.next = right;
                right = right.next;
            }
            tail = tail.next;
        }

        if(left!=null) tail.next = left;
        if(right!=null) tail.next = right;

        return ans.next;
    }

    public static void main(String[] args) {
        int[] vals = {4,2,1,3};
        ListNode head = ListNode.create(vals);
        LC148 t = new LC148();
        ListNode ans = t.sortList(head);
        if(ans==null) System.out.println("null");
        else ans.show();
    }
}

遇事不要慌,先过了再说

  • 暴力通过
public ListNode sortList(ListNode head) {
    ArrayList<Integer> list = new ArrayList<>();
    ListNode node = head;
    while (node!=null){
        list.add(node.val);
        node = node.next;
    }
    //Integer[] arr = list.toArray(new Integer[list.size()]);
    Collections.sort(list);
    node = head;
    for (Integer val :list){
        node.val=val;
        node=node.next;
    }
    return head;
}

接下来再考虑优化

归并排序 O(n*logn) , 是可以应用于链式结构的
但是 归并,用到递归,空间复杂度目前还是O(logn)

  • 归并排序:自顶向下归并排序(递归)-》空间logn 时间O(nlogn)
public ListNode sortList(ListNode head) {
    return sortList(head, null); // 左闭右开的好处 这里直接传一个null就行了 下面都保持左闭右开 又不是不能写了
}

// 注意 这条链的区间是 head~tail.pre
public ListNode sortList(ListNode head, ListNode tail) {
    if (head == null) return head;
    if(head.next == tail) {//递归边界 及时处理  此时就只有一个结点了  (注意都是左闭右开 head.next == )
        head.next = null; // 直接单个结点作为一条链返回
        return head; // 单个结点就是自己一条链 已经有序了 不需要merge归并
    }
	
	//快慢指针找中点 
    ListNode slow=head,fast = head;
    while (fast!=tail){//直接把tail想像成逻辑上的null即可
        slow = slow.next;
        fast = fast.next;
        if(fast!=tail) fast = fast.next;
    }
    ListNode mid = slow;
    return merge(sortList(head,mid),sortList(mid,tail));//都是左闭右开
}

// 合并之后要返回一条新的链表 //都是null结尾
public ListNode merge(ListNode headA, ListNode headB) {
    ListNode head = new ListNode();
    ListNode tail = head,tailA = headA,tailB=headB;//命名为tail 就不会忘记tail=tail.next的移动了
    while (tailA!=null&&tailB!=null){
        if(tailA.val<=tailB.val){
            tail.next = tailA;
            tailA = tailA.next;//别忘了
        }else {
            tail.next = tailB;
            tailB = tailB.next;//别忘了
        }
        tail = tail.next;
    }
    if(tailA!=null) tail.next = tailA;
    else tail.next = tailB;//就算tailB是null也无所谓  //最后都要以null结尾啊
    return head.next;//去掉头节点
}
  • 归并排序:自底向上归并排序 -》空间O(1) 时间O(nlogn) 其实就是改为非递归写法

for循环分别 11合并 22合并 44合并 … 直到len/2,len/2合并

这样就节省了空间了

public ListNode sortList(ListNode head) {
    if(head==null) return null;
	
	// 计算链表长度
    int len = 0;
    ListNode node = head;
    while (node != null) {
        len++;
        node=node.next;
    }

    ListNode preHead = new ListNode(-1,head);//加一个头节点 或者说单独开一条preHead为首的链 后面全部链在头结点后面
    for (int step = 1; step < len; step<<=1) {//step*=2  注意是<<=1 不是 <<=2
        ListNode preTail = preHead;
        ListNode curr = preHead.next;

        while (curr!=null){//多次两两归并  curr记录当前归并到哪里了
            // 1、找step个元素 单独拆下来成一条链
            ListNode head1 = curr;
            // i要从1开始 因为curr已经是一个元素了
            for(int i=1;i<step&&curr!=null;i++){//注意一下 左毕右开 curr.next!=null
                curr = curr.next;
            }
            ListNode next = null;//后面可能没完 还要接着合并呢 记录下来本次归并到哪里了
            if(curr!=null){//curr为null就没有next了
                next = curr.next;
                curr.next = null;//head1成单独一条链
            }

            // 2、接着找step个元素 拆下来成另一条链
            ListNode head2 = next;//head2的起点 //注意先扯断链  再给新起点
            curr = head2;
            for(int i=1;i<step&&curr!=null;i++){//curr!=null 防止上来就是null
                curr = curr.next;
            }
            next = null;//后面可能没完 还要接着合并呢 记录下来本次归并到哪里了
            if(curr!=null){//curr为null就没有next了
                next = curr.next;
                curr.next = null;//head2成单独一条链  整个next之前的都会被归并 null是终点嘛 所以下次从next开始归并就行了
            }

            // 3.、归并两条链 一次连在preh后面
            preTail.next = merge(head1,head2);
            while (preTail.next!=null) preTail=preTail.next;//再指向链尾 下次接着连呀
            curr = next;//接着往后归并啊
        }
    }

    return preHead.next;

}

// 合并之后要返回一条新的链表 //都是null结尾
public ListNode merge(ListNode headA, ListNode headB) {
    ListNode head = new ListNode();
    ListNode tail = head,tailA = headA,tailB=headB;//命名为tail 就不会忘记tail=tail.next的移动了
    while (tailA!=null&&tailB!=null){
        if(tailA.val<=tailB.val){
            tail.next = tailA;
            tailA = tailA.next;//别忘了
        }else {
            tail.next = tailB;
            tailB = tailB.next;//别忘了
        }
        tail = tail.next;
    }
    if(tailA!=null) tail.next = tailA;
    else tail.next = tailB;//就算tailB是null也无所谓  //最后都要以null结尾啊
    return head.next;//去掉头节点
}

节省了空间,但比递归要慢一点

152. 乘积最大子数组

152. 乘积最大子数组

  • 老规矩 先暴力 通过再说
// 老规矩 先暴力
public int maxProduct(int[] nums) {
    int max = Integer.MIN_VALUE;
    for (int i = 0; i < nums.length; i++) {
        int sum = 1;
        for (int j = i; j < nums.length; j++) {
            sum *= nums[j];
            max = Math.max(max,sum);
        }
    }
    return max;
}
  • 其实也是很简单的dp : 同时维护以i结尾的连续子数组 的最大和最小值 2个数组即可

nums[i]是正数 那么max可能是 nums[i]*dpMax[i-1] min可能是nums[i]*dpMin[i-1]
nums[i]是负数 那么max可能是 nums[i]*dpMin[i-1] min可能是nums[i]*dpMax[i-1]

同时维护两个dp数组

// 其实也是很简单的dp : 同时维护以i结尾的连续子数组 的最大和最小值 数组即可
public int maxProduct(int[] nums) {
    int max = nums[0];
    int[] dpMax = new int[nums.length];
    int[] dpMin = new int[nums.length];
    dpMax[0] = nums[0];
    dpMin[0] = nums[0];

    for (int i = 1; i < nums.length; i++) {
        if(nums[i]>0){
            dpMax[i] = Math.max(nums[i],nums[i]*dpMax[i-1]);
            dpMin[i] = Math.min(nums[i],nums[i]*dpMin[i-1]);
        }else {//<=0
            dpMax[i] = Math.max(nums[i],nums[i]*dpMin[i-1]);
            dpMin[i] = Math.min(nums[i],nums[i]*dpMax[i-1]);
        }
        max = Math.max(max,dpMax[i]);
    }
    return max;
}

语法层面优化一下:

第一步优化:
在这里插入图片描述

第二步优化: 每次dp[i]都只用得到dp[i-1] 所以可以使用一维滚动数组 节省空间
在这里插入图片描述

// 其实也是很简单的dp : 同时维护以i结尾的连续子数组 的最大和最小值 数组即可
public int maxProduct(int[] nums) {
    int max = nums[0],dpMax = nums[0],dpMin = nums[0];
    for (int i = 1; i < nums.length; i++) {
        int mm = dpMax,mn=dpMin;//得先咱暂存下来 否则 第一个dpMax赋值完毕 原来的dpMax值丢失了 dpMin就没法子求了
        dpMax = Math.max(nums[i], Math.max(nums[i] * mm, nums[i] * mn));
        dpMin = Math.min(nums[i], Math.min(nums[i] * mm,nums[i] * mn));
        max = Math.max(max, dpMax);
    }
    return max;
}

155. 最小栈

155. 最小栈

不难,双栈维护即可,其中一个mins栈始终重复地压入目前维护的最小值即可

class MinStack {
    LinkedList value;
    LinkedList mins;
    int min;

    public MinStack() {
        value = new LinkedList<Integer>();
        mins = new LinkedList<Integer>();
        min = Integer.MAX_VALUE;
    }

    public void push(int val) {
        min = Math.min(val,min);
        value.add(val);
        mins.add(min);//维护当前最小值即可 最小值被pop之前 都是这个值最小
    }

    public void pop() {
        value.removeLast();
        mins.removeLast();
        //注意这里也要更新min
        if(mins.isEmpty()) min = Integer.MAX_VALUE;
        else min = getMin();
    }

    public int top() {
        return (int) value.getLast();//栈都是在末尾操作 一端操作
    }

    public int getMin() {
        return (int) mins.getLast();
    }
}

效率有点低,看题解再优化一下吧:

快了1ms

trick: mins栈 底部 始终多维护一个MAX_VALUE

import java.util.LinkedList;

class MinStack {
    LinkedList value;
    LinkedList mins;

    public MinStack() {
        value = new LinkedList<Integer>();
        mins = new LinkedList<Integer>();
        mins.add(Integer.MAX_VALUE);//技巧 栈底部始终多维护一个MAX_VALUE
    }

    public void push(int val) {
        value.add(val);
        mins.add(Math.min(val,getMin()));//维护当前最小值即可 最小值被pop之前 都是这个值最小
    }

    public void pop() {
        value.removeLast();
        mins.removeLast();
    }

    public int top() {
        return (int) value.getLast();//栈都是在末尾操作 一端操作
    }

    public int getMin() {
        return (int) mins.getLast();
    }
}

160. 相交链表

160. 相交链表

408真题,挺水的了

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    int lenA = 0, lenB = 0;
    ListNode node1 = headA, node2 = headB;
    while (node1 != null) {
        lenA++;
        node1 = node1.next;
    }
    while (node2 != null) {
        lenB++;
        node2 = node2.next;
    }

    // 保证node1是长的那个
    node1 = headA;
    node2 = headB;//注意把值赋回来
    if (lenA < lenB) {
        ListNode tmp = node1;
        node1 = node2;
        node2 = tmp;
    }

    int T = Math.abs(lenA - lenB);
    while (T-->0) {
        node1 = node1.next;
    }

    while (node1 != node2) {
        node1 = node1.next;
        node2 = node2.next;
    }
    return node1;
}

其实有一种巧妙的省事的写法

A,B 同时移动
谁先到null就指向另一个头部,然后继续同步后移
另一个到头了,这个就恰好走了差值个步数,再同步即可相遇

加入A长B短

图片来自leetcode题解
在这里插入图片描述
从尾到头也算一步,另一个指针跟着走一步

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    ListNode pa = headA, pb = headB;
    while (pa != pb) {
        pa = pa == null ? headB : pa.next; //pa可以为null 否则无公共点时不好返回
        pb = pb == null ? headA : pb.next;
    }
    return pa;
}

169. 多数元素

169. 多数元素

408 真题,现在做就很水了,维护一个主元素,和计数器,必要时更新主元素即可

public int majorityElement(int[] nums) {
    int provit = nums[0];
    int count = 1;
    for (int i = 1; i < nums.length; i++) {
        if(nums[i]==provit){
            count++;
        }else {
            if(count==1) provit = nums[i];
            else count--;
        }
    }
    return provit;
}

198. 打家劫舍

198. 打家劫舍

终于自己调出来了一个dp

public int rob(int[] nums) {
    int n = nums.length;
    int[] dp = new int[n];
    dp[0] = nums[0];
    for (int i = 1; i < n; i++) {
        int a = i >= 2 ? dp[i - 2] : 0;
        int b = i >= 3 ? dp[i - 3] : 0;
        dp[i] = nums[i] + Math.max(a, b);
    }
    return Math.max(dp[n - 1], n >=2 ? dp[n - 2] : 0);//最后两个可能最大 (相邻两个维护最大)
}

滚动数组,优化空间:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值