leetcode hot100(1)

1.160.相交链表

(1)暴力解法

循环遍历listA的所有节点,循环内遍历B所有节点,检查当前遍历到的的A、B中的节点是否一致。

如果一致,标记,跳出循环。

最后根据标记为返回结果。

时间复杂度O(len(A)*len(B))

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        boolean res = false;
        while(headA != null){
            ListNode tempB = headB;
            while(tempB != null){
                if(headA == tempB){
                    res = true;
                    break;
                }
                tempB = tempB.next;
                
            }
            if(res){
                break;
            }
            headA = headA.next;
            
        }
        if(res) return headA;
        return null;
    }
}

(2)双指针法

双指针法利用的是对称性。

从对称性角度来理解这种解法:A链表长a+c,B链表长b+c。想要让两个head走相同的步数,并且同时到达相交节点,只要headA再走b步,headB再走A步。那么只需要headA挪到原来的headB处,headB挪到原来的headA处。

检查,当没有交点的时候,都走m+n时,会都为null。

最坏时间复杂度O(len(A) + len(B))

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode tempA = headA , tempB = headB;
        while(tempA != tempB){
            if(tempA == null){
                tempA = headB;
            }else{
                tempA = tempA.next;
            }
            if(tempB == null){
                tempB = headA;
            }else{
                tempB = tempB.next;
            }
            
            
        }
        return tempA;
    }
}

(3)哈希法

时间复杂度O(len(A) + len(B))

空间复杂度O(len(A))

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        Set<ListNode> set = new HashSet<>();
        while(headA!= null){
            set.add(headA);
            headA = headA.next;
        }
        while(headB!=null){
            if(set.contains(headB)){
                return headB;
            }else{
                headB = headB.next;
            }
        }
        return null;
    }
}

2.二叉树的最近公共祖先

(1)递归思想

思路:使用递归实现二叉树的从下向上的搜索(后序遍历)

递归三部曲:

a.确定参数及返回值

b.确定终止条件

这里的终止条件是遍历到的结点如果是p或者是q或者是null,就返回该节点

c.确定单层的逻辑

单层逻辑这里包含着最近公共祖先的判断逻辑:接到左右子树分别递归回来的返回结果。如果左、右均不为空,说明该节点就是最近的公共祖先(依据后序遍历的顺序)。如果不满足上面的条件,该节点需要继续把子结点的结果传上去,返回不为空的那个结果(都为空就返回null)

java 复制代码
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root == p || root == q) return root;
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        if(left != null && right != null) return root;
        else if (left != null && right == null) return left;
        else if (left == null && right != null) return right;
        else return null;
    }
}

3.回文链表

(1)复制到数组中用双指针法(下面是复制到了ArrayList中)

空间复杂度O(n),时间复杂度O(n)

java 复制代码
/**
 * 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 boolean isPalindrome(ListNode head) {
        boolean flag = true;
        List<Integer> list = new ArrayList<>();
        while(head != null){
            list.add(head.val);
            head = head.next;
        }
        int low = 0 , high = list.size() - 1;
        while(low < high){
            if(list.get(low) != list.get(high)){
                flag = false;
            }
            low++;
            high--;
        }
        return flag;
    }
}

(2)希望避免O(n)的空间复杂度,我们直接修改链表的结构,但这也会造成并发时需要加锁的问题:

4.739. 每日温度

(1)暴力搜索,但是会时间超限,时间复杂度O(n^2)

java 复制代码
public int[] dailyTemperatures(int[] temperatures) {
        int[] res = new int[temperatures.length];
        for(int i = 0 ; i < temperatures.length ; i++){
            for(int j = i+1 ; j < temperatures.length ;j++){
                if(temperatures[j] > temperatures[i]){
                    res[i] = j - i;
                    break;
                }
            }
        }
        return res;
    }

(2)这属于"下一个更大"的问题,可以考虑使用单调栈,用空间换取时间。

我们需要记忆的是目前还无法确定的,维护一个从栈底到栈顶单调递减的栈,直到下一个元素大于栈内,把栈内所有满足小于该元素的成员弹出,并得到这些成员的结果。然后该元素入栈,重复上述行为。

元素的大小可以通过temperatures数组获取,我们要维护的是元素的下标。

单调栈的思想通常用于寻找"下一个更大"或者"下一个更小"的问题,核心在于用空间换取时间,实现一次遍历,记录下目前还不能确定答案,能在未来确定答案的元素。

java 复制代码
class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int[] res = new int[temperatures.length];
        Deque<Integer> stack = new ArrayDeque<>();
        stack.push(0);
        for(int index = 1 ; index < temperatures.length ; index++){
            while(!stack.isEmpty() && temperatures[index] > temperatures[stack.peek()]){
                res[stack.peek()] = index - stack.peek();
                stack.pop();
            }
            stack.push(index);
        }
        return res;
    }
}

总结起来,单调栈的应用场景就是:在O(n)的时间内实现寻找下一个更大或下一个更小。

5.226.翻转二叉树

二叉树有天然的递归结构。

翻转二叉树,按照先序或者后序遍历的顺序翻转均可。

中序遍历翻转的话会有问题,它不是按照递归的顺序翻转的。

可以分解一下这道题,使解法更加清晰

(1)大框架是遍历,preOrder或者postOrder

(2)order的时候要做reverse,交换左右节点的位置

java 复制代码
public TreeNode invertTree(TreeNode root) {
        if(root == null) return root;
        preOrder(root);
        return root;
    }

    public void preOrder(TreeNode root){
        if(root == null) return;
        reverse(root);//先序遍历,本层逻辑
        preOrder(root.left);
        preOrder(root.right);

    }

    public void reverse(TreeNode root){
        if(root == null) return;
        TreeNode tmp = root.left;
        root.left = root.right;
        root.right = tmp;
    }

6.221.最大正方形

(1)暴力搜索

java 复制代码
class Solution {
    public int maximalSquare(char[][] matrix) {
        int maxSide = 0;
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return maxSide;
        }
        int rows = matrix.length;
        int cols = matrix[0].length;
        for(int i = 0 ; i < rows; i++){
            for(int j = 0 ;j < cols; j++){
                if(matrix[i][j] == '1'){
                    maxSide = Math.max(maxSide,1);
                    int currentPossibleMaxside = Math.min(rows - i , cols - j );
                    for(int k = 1 ; k < currentPossibleMaxside ; k++){
                        boolean flag = true;
                        for(int m = 0 ; m <= k; m++){
                            if(matrix[i + k][j + m] == '0' || matrix[i + m][j + k] =='0'){
                                flag = false;
                                break;
                            }
                        }
                        if(flag == false){
                            break;
                        }else{
                            maxSide = Math.max(maxSide,k+1);
                        }
                    }
                }
                
                
            }
        }
        return maxSide*maxSide;
    }
    
}

(2)这种暴力搜索问题,通常会考虑记忆化降低时间复杂度,进而归结到动态规划。

动态规划五部曲:

  • 确定dp数组及下标含义

  • 确定递推方程

  • 确定如何初始化

  • 确定递推顺序

  • dp模拟(dp模拟要做的就是打印前几步dp数组,看看和自己预想的一样不一样,如果不一样再找问题)

a. dp数组的含义是和递推顺序也有关系的。我们按照从上到下,从左到右的顺序去遍历二维矩阵。dp[i][j]定义为:以下标i,j为右下角,所能构成的满足题目要求的最大正方形的边长。

b.递推方程。递推方程要分类讨论,结合当前访问的元素的情况;同时也与递推顺序有关,递推方程是原先"记忆化"精练出来的,体现了原来"记忆化"的痕迹。

如果当前位置内容是'0',那么以当前位置为右下角,不可能构成满足题意的正方形,dp[i][j] = 0.

如果当前位置是'1':

java 复制代码
class Solution {
    public int maximalSquare(char[][] matrix) {
        int[][] dp = new int[matrix.length][matrix[0].length];
        for(int i = 0 ; i < matrix.length ; i++){
            if(matrix[i][0] == '1'){
                dp[i][0] = 1;
            }
        }
        for(int j = 0 ; j < matrix[0].length ; j++){
            if(matrix[0][j] == '1'){
                dp[0][j] = 1;
            }
        }
        for(int i = 1 ; i < matrix.length ; i++){
            for(int j = 1 ; j < matrix[0].length ; j++){
                if(matrix[i][j] == '0'){
                    dp[i][j] = 0;
                }else{
                    dp[i][j] = Math.min(dp[i-1][j],Math.min(dp[i][j-1],dp[i-1][j-1])) + 1;
                }
            }
        }
        int maxSide = 0 ;
        for(int i = 0 ; i < matrix.length ; i++){
            for(int j = 0 ;j < matrix[0].length ; j++){
                maxSide = Math.max(maxSide,dp[i][j]);
            }
        }
        return maxSide*maxSide;

    }
    
}

时间复杂度O(m*n),空间复杂度O(m*n)

类似题目:1277.统计全为1的正方形个数

这道题用的dp递推方程和最大正方形的递推方程一样。原因是dp[i][j]既然表示了以i,j下标元素为右下角的最大正方形边长,那么它也就表示了以i,j下标元素为右下角的正方形个数。

最后把dp[i][j]累加起来即为答案。

这里如果按照之前单独初始化的方式,需要考虑许多边界条件(因为涉及ans的累加),这里把初始化的逻辑放在了遍历计算dp里。

java 复制代码
public int countSquares(int[][] matrix) {
        int rows = matrix.length;
        int cols = matrix[0].length;
        int[][] dp = new int[rows][cols];
        
        int ans = 0 ;
        for(int i = 0 ; i < rows ; i++){
            for(int j = 0 ; j < cols ; j++){
                if(i == 0 || j == 0){
                    dp[i][j] = matrix[i][j];
                }else if(matrix[i][j] == 0){
                    dp[i][j] = 0;
                }else{
                    dp[i][j] = Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]) + 1;
                }
                ans += dp[i][j];
            }
        }
        return ans;

    }

7.215.数组中的第K个最大元素

(1)暴力解法

先排序,后返回

平均时间复杂度O(nlogn),不符合题目要求

空间复杂度O(1)

(2)用空间换取时间,把遍历数组的过程看成是读入数据的过程,在这期间维护一个堆。

TopK问题,考虑使用堆作为数据结构,本题中维护一个大小为k的最小堆,读取完数据后在堆顶的即为第k大的数据。

时间复杂度和空间复杂度是由堆这种数据结构的性质决定的。

时间复杂度O(nlogk)

空间复杂度O(k)

java 复制代码
class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> heap = new PriorityQueue<>((n1,n2) ->n1-n2);
        for (int num : nums) {
            heap.add(num);
            if(heap.size() > k){
                heap.poll();
            }
        }
        return heap.poll();
    }
}

(3)快速选择算法

其核心思想是分治,使用递归实现。

(可以证明,)平均情况下,快速选择算法可以实现O(n)的时间复杂度,最坏情况为O(n^2)

快速排序模板:

java 复制代码
public static void main(String[] args) {
        int[] nums= {3,2,3,1,2,4,5,5,6};
        quickSort(nums,0,nums.length-1);
        for (int num : nums) {
            System.out.println(num);
        }
    }

    public static void quickSort(int[] nums,int l , int h){
        if(l >= h) return ;
        int p = partition2(nums,l,h);
        quickSort(nums,l,p-1);
        quickSort(nums,p+1,h);
    }


    

    public static int partition2(int[] nums,int l , int h){
        int i = l, j = h;
        int pivot = nums[i];
        while(i < j){
            while(i < j && nums[j] > pivot) j--;
            while(i < j && nums[i] <= pivot) i++;
            swap(nums,i,j);
        }
        swap(nums,l,j); //此时i和j重合
        return j;//此时i和j重合
    }


    public static void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

应用到快速选择解决topK问题上:

java 复制代码
class Solution {
    public int findKthLargest(int[] nums, int k) {
        k = nums.length - k + 1;
        return quickSort3(nums,0,nums.length-1,k);
    }
    public static int quickSort3(int[] nums,int l , int h , int k){
        if(l >= h) return nums[l];
        int p = partition2(nums,l,h);
        //下标为p处,前面有p个元素
        if(p == k-1) return nums[p];
        else if(p > k-1) return quickSort3(nums,l,p-1,k);
        else return quickSort3(nums,p+1,h,k);

    }

    public static int partition2(int[] nums,int l , int h){
        int i = l, j = h;
        int pivot = nums[i];
        while(i < j){
            while(i < j && nums[j] > pivot) j--;
            while(i < j && nums[i] <= pivot) i++;
            swap(nums,i,j);
        }
        swap(nums,l,j); //此时i和j重合
        return j;//此时i和j重合
    }

    public static void swap(int[] nums,int i , int j){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

8.208.实现Trie(前缀树)

前缀树是一种非典型多叉树,适合用于保存需要频繁查询,以及频繁查询前缀的若干字符串。

前缀树的结点设计是一个字母表加上isEnd标志位。

对于sea,sells,she三个字符串,存储类似于:

前缀树的结构是固定的,节点是否为空代表该字母在该位置是否存储在了前缀树里。

java 复制代码
package org.example.quickSort;

public class Trie {
    private Trie[] children;
    private  boolean isEnd;


    public Trie() {
        children =  new Trie[26];
        isEnd = false;

    }

    public void insert(String word) {
        Trie node = this;
        for (char c : word.toCharArray()) {
            int index = c - 'a';
            if(node.children[index] == null){
                node.children[index] = new Trie();
            }
            node = node.children[index];

        }
        node.isEnd = true;

    }

    public boolean search(String word) {
        Trie node = this;
        for (char c : word.toCharArray()) {
            int index = c - 'a';
            if(node.children[index] == null){
                return false;
            }
            node = node.children[index];
        }
        return node.isEnd;


    }

    public boolean startsWith(String prefix) {
        Trie node = this;
        for (char c : prefix.toCharArray()) {
            int index = c - 'a';
            if(node.children[index] == null){
                return false;
            }
            node = node.children[index];
        }
        return true;
    }
}

9.207.课程表

这道题根据题面描述就是一道拓扑排序问题

拓扑排序判断结果是否包含所有courses

拓扑排序解法过程要注意一下几点:

a.记录inDegree(通过数组)

b.记录当前节点指向的下一节点都有哪些 (通过map)

c.需要一个队列来维护已知的待处理的 目前入度为0的节点,结果通过一个List返回

java 复制代码
class Solution {
    public static boolean canFinish(int numCourses, int[][] prerequisites) {
        List<Integer> res = new ArrayList<>();
        int[] inDegree = new int[numCourses];
        Map<Integer,List<Integer>> nextCourses = new HashMap<>();
        Deque<Integer> queue = new ArrayDeque<>();
        for(int i = 0 ; i < numCourses ; i++){
            nextCourses.put(i,new ArrayList<>());
        }
        for(int i = 0 ; i < prerequisites.length ; i++){
            int fromNode = prerequisites[i][0];
            int toNode = prerequisites[i][1];
            nextCourses.get(fromNode).add(toNode);
            inDegree[toNode]++;
        }
        for(int i = 0 ; i < numCourses ; i++){
            if(inDegree[i] == 0){
                queue.add(i);
            }
        }

        while(!queue.isEmpty()){
            int node = queue.poll();
            res.add(node);
            //删除该节点的所有指向其他节点的入度
            List<Integer> list = nextCourses.get(node);
            for (Integer i : list) {
                inDegree[i]--;
                if(inDegree[i] == 0){
                    queue.add(i);
                }
            }
        }
        if(res.size() == numCourses){
            return true;
        }

        return false;
    }
}

10.206.反转链表

考查对链表这种数据结构的操作能力。

java 复制代码
public ListNode reverseList(ListNode head){
        ListNode prev = null;
        ListNode cur = head;
        while(cur != null){
            ListNode next = cur.next;
            cur.next = prev;
            prev = cur;
            cur = next;
        }
        return prev;
    }
相关推荐
西几1 小时前
代码训练营 day48|LeetCode 300,LeetCode 674,LeetCode 718
c++·算法·leetcode
liuyang-neu1 小时前
力扣第420周赛 中等 3324. 出现在屏幕上的字符串序列
java·算法·leetcode
想做白天梦2 小时前
双向链表(数据结构与算法)
java·前端·算法
小卡皮巴拉2 小时前
【力扣刷题实战】相同的树
c语言·算法·leetcode·二叉树·递归
zyhomepage2 小时前
科技的成就(六十四)
开发语言·人工智能·科技·算法·内容运营
想做白天梦2 小时前
多级反馈队列
java·windows·算法
潇雷2 小时前
算法Day12|226-翻转二叉树;101-对称二叉树;104-二叉树最大深度;111-二叉树最小深度
java·算法·leetcode
爱编程— 的小李3 小时前
开关灯问题(c语言)
c语言·算法·1024程序员节
韭菜盖饭3 小时前
LeetCode每日一题3211---生成不含相邻零的二进制字符串
数据结构·算法·leetcode
manba_3 小时前
leetcode-146. LRU 缓存
leetcode