leetCode刷题记录1

news2024/11/25 4:44:10

文章目录

    • hot100题
      • 200. 岛屿数量
      • 206. 反转链表
      • 207. 课程表
      • 208. 实现 Trie (前缀树)
        • ★ Student[] strArr= new Student[26];//真的只是创建了引用数组 只是new了数组存放引用的内存 并没有真的new对象内存
      • 215. 数组中的第K个最大元素
      • 221. 最大正方形
      • 226. 翻转二叉树
      • 234. 回文链表
      • 236. 二叉树的最近公共祖先
      • 238. 除自身以外数组的乘积
      • 240. 搜索二维矩阵 II
      • 279. 完全平方数
      • 283. 移动零
      • 287. 寻找重复数
      • 300. 最长递增子序列
      • 309. 最佳买卖股票时机含冷冻期
      • 322. 零钱兑换
      • 337. 打家劫舍 III
      • 338. 比特位计数
      • 347. 前 K 个高频元素
      • 394. 字符串解码
      • 406. 根据身高重建队列
      • 416. 分割等和子集
      • 437. 路径总和 III
      • 438. 找到字符串中所有字母异位词
      • 448. 找到所有数组中消失的数字
      • 461. 汉明距离
      • 494. 目标和
      • 538. 把二叉搜索树转换为累加树
      • 543. 二叉树的直径

hot100题

200. 岛屿数量

200. 岛屿数量

经典老题 D/BFS求连通分量个数
先用BFS写了一下,好多细节错误然后代码写得很糟糕

// 经典老题  D/BFS求连通分量个数
public int numIslands(char[][] grid) {
    N = grid.length;
    M = grid[0].length;
    this.grid = grid;
    visit = new boolean[N][M];
    int k = 0;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (!visit[i][j] && grid[i][j] == '1') {
                BFS(i, j);
                k++;
            }
        }
    }
    return k;
}

class Point {
    int x, y;
    Point() {}
    Point(int x, int y) {this.x = x;this.y = y;}
}

int M,N;
char[][] grid = null;
boolean[][] visit = null;

boolean judge(int x, int y) {
    if (x < 0 || y < 0 || x >= N || y >= M || visit[x][y] || grid[x][y] == '0') return false;//千万注意是字符'0'
    return true;
}

int[] dx = {1, -1, 0, 0};
int[] dy = {0, 0, 1, -1};

void BFS(int x, int y) {
    Queue<Point> q = new LinkedList<>();
    q.offer(new Point(x, y));
    visit[x][y] = true;

    while (!q.isEmpty()) {
        Point top = q.poll();
        //System.out.printf("(%d,%d) ",top.x+1,top.y+1);
        for (int i = 0; i < dx.length; i++) {
            int a = top.x + dx[i], b = top.y + dy[i];
            if (judge(a, b)) {
                q.offer(new Point(a, b));
                visit[a][b] = true;
            }
        }
    }
    //System.out.println();
}

看了题解,再去优化:
1、DFS代码要比BFS简单多了
2、遍历过的直接修改值为非1即可,不用专门准备visit数组了
3、DFS也不需要dx、dy数组,直接写四次dfs递归调用即可,看起来清爽多了

重写了一下,果然舒服多了,不要总想着c++那一套,太麻烦了:

// 经典老题  D/BFS求连通分量个数
public int numIslands(char[][] grid) {
    M = grid.length;
    N = grid[0].length;
    this.grid = grid;
    int res = 0;
    for (int i = 0; i < M; i++) {
        for (int j = 0; j < N; j++) {
            if (grid[i][j] == '1') {//千万注意是字符'1',不是数字1
                DFS(i, j);
                res++;
            }
        }
    }
    return res;
}

int M, N;
char[][] grid;

void DFS(int x, int y) {
    // 千万注意是字符'1',不是数字1
    if (x < 0 || x >= M || y < 0 || y >= N || grid[x][y] != '1') return;
    grid[x][y] = 2;//遍历过了 修改为2 (只要不是1就行了)
    //继续递归遍历连通的1
    DFS(x + 1, y);
    DFS(x - 1, y);
    DFS(x, y + 1);
    DFS(x, y - 1);
}

206. 反转链表

206. 反转链表

// 水题 拆下来 一个个头插 即可
public ListNode reverseList(ListNode head) {
    ListNode ans = new ListNode();//头结点
    while (head!=null){
        // 先摘下来
        ListNode t = head;
        head = head.next;
        // 再头插
        t.next = ans.next;
        ans.next = t;
    }
    return ans.next;
}
  • 据说递归可以做这题

果然可以,类似后序遍历的写法,也很简单

ListNode ans = new ListNode();//头结点
ListNode pre = ans;
public ListNode reverseList(ListNode head) {
    if(head==null) return null;
    reverseList(head.next);
    pre.next = head;
    pre = head;
    pre.next = null;//防止最后一个没有尾巴
    return ans.next;
}

207. 课程表

207. 课程表

// 经典拓扑排序
public boolean canFinish(int numCourses, int[][] prerequisites) {
    int[] indegree = new int[numCourses];
    int[][] matrix = new int[numCourses][numCourses];

    // 领接矩阵
    for (int i = 0; i < prerequisites.length; i++) {
        int a = prerequisites[i][0], b = prerequisites[i][1];
        matrix[b][a] = 1;//b->a   //邻接矩阵 同时也是hash表
        indegree[a]++; // 随便记录每个元素的入度
    }

    Stack<Integer> s = new Stack<>();
    for (int i = 0; i < numCourses; i++) {
        if (indegree[i] == 0) s.push(i);
    }

    int count = 0;
    while (!s.isEmpty()) {
        Integer top = s.pop();
        count++;
        // 所有后继入度-1
        for (int i = 0; i < numCourses; i++) {
            if (top!=i&&matrix[top][i] == 1) {
                indegree[i]--;
                if (indegree[i] == 0) {//入度减为0 没有前驱了  可以去学习啦
                    s.push(i);
                }
            }
        }
    }
    return count == numCourses;//都学过才行
}

好久没写了,效率好低啊。接下来,看题解优化一下:

改成邻接表试试,哇塞,快了5倍欸

时间空间都节省了:
在这里插入图片描述

// 经典拓扑排序
public boolean canFinish(int numCourses, int[][] prerequisites) {
    int[] indegree = new int[numCourses];
    ArrayList<ArrayList<Integer>> list = new ArrayList<>();//邻接表

    for (int i = 0; i < numCourses; i++) {
        list.add(new ArrayList<Integer>());
    }

    // 领接矩阵
    for (int i = 0; i < prerequisites.length; i++) {
        int a = prerequisites[i][0], b = prerequisites[i][1];
        list.get(b).add(a);//b->a   //邻接表 同时也是hash表
        indegree[a]++; // 随便记录每个元素的入度
    }

    Stack<Integer> s = new Stack<>();
    for (int i = 0; i < numCourses; i++) {
        if (indegree[i] == 0) s.push(i);
    }

    int count = 0;
    while (!s.isEmpty()) {
        Integer top = s.pop();
        count++;
        // 所有后继入度-1 //邻接表直接能找到
        for (Integer e : list.get(top)) {
            if(--indegree[e]==0){
                s.push(e);
            }
        }
    }
    return count == numCourses;//都学过才行
}

208. 实现 Trie (前缀树)

208. 实现 Trie (前缀树)

  • 老规矩,先暴力通过

每次缓存所有前缀,牺牲维护的时间,以及全局的空间

class Trie {
    HashMap<String,Integer> hash;//1存在(也算一种前缀)  2前缀
    public Trie() {
        hash = new HashMap<String,Integer>();
    }
    public void insert(String word) {
        hash.put(word,1);
        for (int i = 1; i < word.length(); i++) {
            String prefix = word.substring(0, i);
            if(!search(prefix)&&!startsWith(prefix)) hash.put(prefix,2);//所有前缀设置为2  (已经为1的不能退化为2  已经是前缀的也不需要重复put了)
        }
    }
    public boolean search(String word) {
        Integer x = hash.get(word);
        return x!=null && (x==1);
    }
    public boolean startsWith(String prefix) {
        Integer x = hash.get(prefix);
        return x!=null && (x==1||x==2);//存在也算前缀
    }
}
class Trie2 {

    Set<String> set = new HashSet<>();
    public Trie2() {}

    public void insert(String word) {
        set.add(word);
    }

    public boolean search(String word) {
        return set.contains(word);
    }

    public boolean startsWith(String prefix) {
        if(set.contains(prefix)) return true;
        for (String s : set) {
            if(s.startsWith(prefix)) return true;
        }
        return false;
    }
}

其实你想想jdk1.7 ConcurrentHashMap,可不可以类似地直接一个用一个长度为26的大数组,首字母作为key, 每个大数组下面若干小数组,每个小数组又可以拉链,似乎真的可以

但本题当然没这么简单,本题是一个26叉树,每个结点都有26个成员,树构建好了,就简单了。

不要怕空间,每个结点26长的数组也不算大了,不就是26叉树吗,和二叉树没啥区别呀

核心思想:
前缀树:1、26叉树 2、结构如下

struct TrieNode {
    bool isEnd; //该结点是否是一个串的结束
    TrieNode* next[26]; //字母映射表
};
  • 26叉树 实现前缀树 tries

时间和空间上都快多了

在这里插入图片描述

class Trie {

    private Trie[] next; //下标即是对应字符  null表示没有  !null表示有
    private boolean isEnd; // 默认false


    public Trie() {// new Trie(); 才是调用构造方法创建对象
        next = new Trie[26];//真的只是创建了引用数组 只是new了数组存放引用的内存 并没有真的new对象内存
        isEnd = false;

    }
    public void insert(String word) {
        Trie node = this;
        for (int i = 0; i < word.length(); i++) {
            int x = word.charAt(i)-'a';
            if(node.next[x]==null){
                node.next[x] = new Trie();
            }
            node = node.next[x];
        }
        node.isEnd = true;
    }

    public boolean search(String word) {
        Trie node = this;
        for (int i = 0; i < word.length(); i++) {
            int x = word.charAt(i)-'a';
            if(node.next[x]==null){
                return false;
            }
            node = node.next[x];
        }
        return node.isEnd;

    }
    public boolean startsWith(String prefix) {
        Trie node = this;
        for (int i = 0; i < prefix.length(); i++) {
            int x = prefix.charAt(i)-'a';
            if(node.next[x]==null){
                return false;
            }
            node = node.next[x];
        }
        return true;

    }
}
  • 上面两个方法有大量重复代码,其实可以抽取复用的
class Trie {

    private Trie[] next; //下标即是对应字符  null表示没有  !null表示有
    private boolean isEnd; // 默认false


    public Trie() {// new Trie(); 才是调用构造方法创建对象
        next = new Trie[26];//真的只是创建了引用数组 只是new了数组存放引用的内存 并没有真的new对象内存
        isEnd = false;

    }
    public void insert(String word) {
        Trie node = this;
        for (int i = 0; i < word.length(); i++) {
            int x = word.charAt(i)-'a';
            if(node.next[x]==null){
                node.next[x] = new Trie();//这是才是创建对象内存
            }
            node = node.next[x];
        }
        node.isEnd = true;
    }

    public boolean search(String word) {
        Trie trie = searchPrefix(word);
        return trie!=null && trie.isEnd;
    }
    public boolean startsWith(String prefix) {
        return searchPrefix(prefix) != null;
    }

    // 上面两个方法代码重复了 (复制粘贴固然方便 抽取通用方法 复用 才是最好的实现方式)
    public Trie searchPrefix(String prefix){
        Trie node = this;
        for (int i = 0; i < prefix.length(); i++) {
            int x = prefix.charAt(i)-'a';
            if(node.next[x]==null){
                return null;
            }
            node = node.next[x];
        }
        return node;
    }
}

★ Student[] strArr= new Student[26];//真的只是创建了引用数组 只是new了数组存放引用的内存 并没有真的new对象内存

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

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

自己的思路,只有快速选择算法了,放缩法能证明<c*n c是常数 2或者3
是O(n)

  • 类似快排 写一个快速选择算法
// 类似快排 写一个快速选择算法
public int findKthLargest(int[] nums, int k) {
    return findKthLargest(nums,0,nums.length-1,k-1);
}

// 类似快排 写一个快速选择算法
public int findKthLargest(int[] nums, int l,int h,int k) {
    int povit = quickSelect(nums,l,h);
    if(povit==k) return nums[povit];
    else if(povit < k) return findKthLargest(nums,povit+1,h,k);
    else return findKthLargest(nums,l,povit-1,k);
}

public int quickSelect(int[] nums, int l,int h){
    int provit = nums[l];
    while (l<h){
        while (l < h&&nums[h]<=provit) h--;
        nums[l] = nums[h];
        while (l<h&&nums[l]>provit) l++;
        nums[h] = nums[l];
    }
    nums[l]=provit;
    return l;
}

还有一处可以优化, 就是划分基准,上面的写法总是默认第一个,其实每次随机选择一个,这里测试的速度显示,会快2~3倍
随机选择一个作为基准没那么复杂,就是每次随机在范围内挑选一个元素,然后和nums[l]进行交换即可,也就是把他交换到数组开头即可。

  • 划分的基准每次随机选择
Random random = new Random();

// 类似快排 写一个快速选择算法
public int findKthLargest(int[] nums, int k) {
    return findKthLargest(nums,0,nums.length-1,k-1);
}

// 类似快排 写一个快速选择算法
public int findKthLargest(int[] nums, int l,int h,int k) {
    int povit = quickSelect(nums,l,h);
    if(povit==k) return nums[povit];
    else if(povit < k) return findKthLargest(nums,povit+1,h,k);
    else return findKthLargest(nums,l,povit-1,k);
}

public int quickSelect(int[] nums, int l,int h){
    int provit = random.nextInt(h - l + 1) + l;
    swap(nums,l,provit);//随机选择一个作为基准 换到l的位置 才能进行快排的划分呐
    int tmp = nums[l];
    while (l<h){
        while (l < h&&nums[h]<=tmp) h--;
        nums[l] = nums[h];
        while (l<h&&nums[l]>tmp) l++;
        nums[h] = nums[l];
    }
    nums[l]=tmp;
    return l;
}

void swap(int[] nums, int a,int b){
    int tmp = nums[a];
    nums[a] = nums[b];
    nums[b] = tmp;
}

堆排序时间复杂度O(nlogn)

221. 最大正方形

221. 最大正方形

直接暴力,果然超时了 (差3组没过)

猜想因该是dp

然后在暴力的基础上进行了剪枝,竟然过了

public int maximalSquare(char[][] matrix) {
    int max = 0;
    for (int i = 0; i < matrix.length; i++) {
        if(matrix.length-i<max) break; //不必要了验证了
        for (int j = 0; j < matrix[i].length; j++) {
            int d = max; //不必要的验证就不用做了 直接找找看有没有更大的
            while (judge(matrix,i,j,d)) d++; //后面也不需要验证了
            max = Math.max(max,d);
        }
    }
    return max*max;
}

boolean judge(char[][] matrix, int x, int y, int d) {
    if(x+d>=matrix.length||y+d>=matrix[0].length) return false;
    for (int i = x; i <= x + d; i++) {
        for (int j = y; j <= y + d; j++) {
            if (matrix[i][j] == '0') return false;//千万注意是跟字符'0'做比较
        }
    }
    return true;
}

这种写法空间上非常优秀,但是时间上还是很慢

  • dp 状态转移方程可以找规律的

在这里插入图片描述
在这里插入图片描述
仔细想想确实这样,因为 三个相邻点的值一定都>=他们3的最小值

改用dp,慢慢优化,最后如下:

public int maximalSquare(char[][] matrix) {
    if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) return 0;
    int m = matrix.length, n = matrix[0].length;
    int[][] dp = new int[m][n];
    int max = 0;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if(matrix[i][j] == '1'){//=='0'不需要处理 因为默认值就是0 节省一点
                if (i < 1 || j < 1) dp[i][j] = 1;
                else dp[i][j] = Math.min(dp[i-1][j], Math.min(dp[i][j-1], dp[i-1][j-1])) + 1;
            }
            max = Math.max(max, dp[i][j]);
        }
    }
    return max*max;
}

题解看到一种二进制做法,很巧妙,可惜时间反而更长了

输入数据1 <= m, n <= 300 所以每行最多300位,long long 也承受不了这么长的二进制,不能直接&,除非大数
自己循环写&,就慢了 所以这种方式不是很好,python天然就是大数,所以不用考虑这个问题,但是python更慢

不考虑溢出的写法如下,不能完全通过。这个方法看起来很妙,其实没有那么可行

public int maximalSquare(char[][] matrix) {
    int max = 0;
    int[] nums = new int[matrix.length];
    for (int i = 0; i < matrix.length; i++) {
        int num = 0;
        for (int j = 0; j < matrix[i].length; j++) {
            num = (num<<1) + (matrix[i][j]-'0');
        }
        nums[i] = num;
    }

    for (int i = 0; i < nums.length; i++) {
        //System.out.print(nums[i]+" ");
        int sum = nums[i];
        for(int j =i;j<nums.length;j++){
            sum &= nums[j];//注意累加地& 各种组合都要遍历到
            if(getOne(sum)<j-i+1) break;;
            max = Math.max(max, j-i+1);
        }
    }
    return max*max;
}

int getOne(int num){
    int k = 0;
    while (num>0){
        num = (num<<1)&num;
        k++;
    }
    return k;//最多有几个连续的1
}

226. 翻转二叉树

226. 翻转二叉树

确实有点水了

public TreeNode invertTree(TreeNode root) {
    if(root==null) return null;

    invertTree(root.left);
    invertTree(root.right);

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

    return root;
}

更牛的写法

public TreeNode invertTree(TreeNode root) {
    if(root==null) return null;

    TreeNode left = invertTree(root.left);
    TreeNode right = invertTree(root.right);

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

    return root;
}

234. 回文链表

234. 回文链表

节省空间O(1), 且时间O(n)

头插法反转后半部分,然后和前半部分一一比较即可

public boolean isPalindrome(ListNode head) {
    ListNode root = new ListNode();

    int k = 0;
    ListNode node = head;
    while (node != null) {
        node = node.next;
        k++;
    }

    int T = k / 2;
    node = head;
    while (T-- > 0) node = node.next;

    if (k % 2 != 0) node = node.next; // 奇数要去掉一个

    // 逆置后半段
    while (node != null) {
        // 先摘下来
        ListNode t = node;
        node = node.next;

        // 再头插法逆置
        t.next = root.next;
        root.next = t;
    }

    // 再做比较
    ListNode l1 = root.next, l2 = head;
    while (l1 != null) {
        if (l1.val != l2.val) return false;
        l1 = l1.next;
        l2 = l2.next;
    }

    return true;
}

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

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

// 先直接BFS找路径吧
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    ArrayList<TreeNode> path1 = dfs(root,p,new ArrayList<TreeNode>());//拿到全路径
    ArrayList<TreeNode> path2 = dfs(root,q,new ArrayList<TreeNode>());
    // 末尾填充1个null 简化比较的逻辑
    path1.add(null);
    path2.add(null);
    for(int i=0;i<path1.size();i++){
        if(path1.get(i)!=path2.get(i)) return path1.get(i-1);
    }
    return null;
}

ArrayList<TreeNode> dfs(TreeNode root,TreeNode p ,ArrayList<TreeNode> list){
    if(root==null) return null;
    list.add(root);
    if(root==p) return new ArrayList<TreeNode>(list);//最后新复制一份返回 防止list退栈后又没了

    ArrayList<TreeNode> left = dfs(root.left,p,list);
    if(left!=null) return left;
    ArrayList<TreeNode> right = dfs(root.right,p,list);
    if(right!=null) return right;

    list.remove(root);//退栈时去掉
    return null;
}
  • 题解有一个有趣的做法,后续遍历查找有无p,q 途中根据左右子树有无p,q就可以判断出结果了

p,q是两个独一无二的结点
左右都返回true,一定一个是有p,另一个有q

// 先直接BFS找路径吧
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    dfs(root, p, q);
    return ans;
}

TreeNode ans;
boolean dfs(TreeNode root,TreeNode p ,TreeNode q){
    if(root==null) return false;

    boolean lson = dfs(root.left,p,q);
    boolean rson = dfs(root.right,p,q);

    //System.out.println(root.val+" "+lson+" "+ rson);

    // 后续遍历 保证了最深 (不看这两个if 就是单纯判断有无p或者q的代码)
    if(lson&&rson) {
        ans = root;
        return true; // 左右各一个 我就是公共祖先
    }

    // 我是p或者q 我下面有另一个结点  那么我就是最近公共
    if((lson||rson)&&(root==p||root==q)) {
        ans = root;
        return true;
    }
    // lson||rson 很重要 左边有 或者右边右 上面就全部返回true  这里有点难
    return lson||rson||root==p||root==q; // 自己不是p或者q但是 左右子树 有p或者q 那么我就是有
}

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

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

在这里插入图片描述

// 二刷还是 不会 关键是要想到计算前后缀呀 (画一个矩阵  每行就是输入数组 对角线全部置为1 然后计算前后缀就简单了)
public int[] productExceptSelf(int[] nums) {
    // 前缀
    int[] prefix = new int[nums.length];
    prefix[0] = 1;
    for (int i = 0; i < nums.length - 1; i++) {
        prefix[i + 1] = prefix[i] * nums[i];
    }

    // 后缀
    int[] suffix = new int[nums.length];
    suffix[nums.length - 1] = 1;
    for (int i = nums.length - 1; i > 0; i--) {
        suffix[i-1] = suffix[i] * nums[i];
    }


    int[] ans = new int[nums.length];
    for (int i = 0; i < ans.length; i++) {
        ans[i] = prefix[i] * suffix[i];
    }

    return ans;
}
  • 节省空间 很容易优化了:
// 二刷还是 不会 关键是要想到计算前后缀呀 (画一个矩阵  每行就是输入数组 对角线全部置为1 然后计算前后缀就简单了)
public int[] productExceptSelf(int[] nums) {
    // 前缀
    int[] prefix = new int[nums.length];
    prefix[0] = 1;
    for (int i = 0; i < nums.length - 1; i++) {
        prefix[i + 1] = prefix[i] * nums[i];
    }
    // 节省空间 直接乘到prefix上面即可
    int suffix= 1;
    for (int i = nums.length - 1; i > 0; i--) {
        suffix *= nums[i];
        prefix[i-1] *= suffix;
    }
    return prefix;
}

240. 搜索二维矩阵 II

240. 搜索二维矩阵 II

剑指里面刷过,所以这里还记得,就很简单了,关键点在于,从左下角开始搜索

// 技巧 从左下角开始搜索
public boolean searchMatrix(int[][] matrix, int target) {
    int m = matrix.length,n=matrix[0].length;
    int i=m-1,j=0;
    while (matrix[i][j]!=target){
        if(matrix[i][j] > target) i--;
        else j++;
        if(i<0||j>=n) return false;
    }
    return true;
}

279. 完全平方数

279. 完全平方数

dp 效率有点低啊

// dp的题目比较难 可能就不需要考虑效率
public int numSquares(int n) {
    int[] dp = new int[n+1];
    for(int i=1;i<=n;i++){
        int min = Integer.MAX_VALUE;
        for(int j=1;j<=Math.sqrt(i);j++){
            min = Math.min(dp[i-j*j],min);//每个可能的平方数都试一次 开始不要考略效率,可能最有效率就不是很好呢
        }//然后这里的最优子结构 用得非常好
        dp[i] = min+1;
    }
    return dp[n];
}

下面想办法提升一下效率

看题解发现,原来还有一个更牛的做法,纯数学定理解决此问题

在这里插入图片描述

  • 四平方和定理
// 四平方和定理 更牛
public int numSquares(int n) {
    if(isOne(n)) return 1;
    if(isTwo(n)) return 2;
    if(isFour(n)) return 4;
    return 3;//3不好判断,但是只剩下他了
}

boolean isOne(int n){
    int k = (int) Math.sqrt(n);
    return k*k==n;
}

boolean isTwo(int n){
    for (int i = 1; i <= Math.sqrt(n); i++) {
        if(isOne(n-i*i)) return true;
    }
    return false;
}

boolean isFour(int n) {
    while (n%4==0) n/=4;
    return n%8==7;
}

这也太快了点吧
在这里插入图片描述

283. 移动零

283. 移动零

  • 方法1,先暴力(冒泡排序)
// 暴力冒泡排序
public void moveZeroes(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        boolean flag = false;
        for(int j=0;j<nums.length-i-1;j++){
            if(nums[j] == 0){
                int t=nums[j+1];
                nums[j+1]=nums[j];
                nums[j]=t;
                flag = true;
            }
        }
        if(!flag) break;
    }
}
  • 方法2:个人觉得还不错,直接搜集非0,后面的全部填充0, 没有交换,直接覆盖
// 直接重新收集非0 直接覆盖 O(n)
public void moveZeroes(int[] nums) {
    int k =0;
    for (int i = 0; i < nums.length; i++) {
        if(nums[i]!=0) nums[k++]=nums[i];
    }
    for(int i=k;i<nums.length;i++) nums[i]=0;
}
  • 方法3:题解方法:双指针,也不错,真的在交换
// 直接重新收取非0 直接覆盖 O(n)
public void moveZeroes(int[] nums) {
    int k =0;
    for (int i = 0; i < nums.length; i++) {
        if(nums[i]!=0) nums[k++]=nums[i];
    }
    for(int i=k;i<nums.length;i++) nums[i]=0;
}

287. 寻找重复数

287. 寻找重复数

必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

题解里好多大神:
个人觉得这两种写法比较好:
1: 转换为:找链表中环的起点问题
2: 范围1~n 可以将每个数字看成指针,对应位置取相反数
某个位置已经被修改为负数了 就直接退出
然后再重复取一次相反数就还原了

两种解法相同的关键点:范围1~n 可以将每个数字看成指针

    1. 找链表中环的起点问题
public int findDuplicate(int[] nums) {
    int fast=0,slow=0;
    do{
        slow = nums[slow];
        fast = nums[nums[fast]];
    }while (slow!=fast);//后验 用do while
    slow = 0;
    while (fast!=slow){
        slow = nums[slow];
        fast = nums[fast];
    }
    return slow;
}
    1. 对应位置取相反数
public int findDuplicate(int[] nums) {
    int res=-1;
    for (int i = 0; i < nums.length; i++) {
        int k = Math.abs(nums[i]);//nums[i]一旦重复 必然给同一个位置 乘上至少2次-1
        if(nums[k]<0){
            res = k;
            break;
        }else {
            nums[k]*=-1;//对应位置乘上-1
        }
    }

    // 不能修改原数组 还原
    for (int i = 0; i < nums.length; i++) {
        int k = Math.abs(nums[i]);//nums[i]一旦重复 必然给同一个位置 乘上至少2次-1
        if(nums[k]>0){
            break;
        }else {
            nums[k]*=-1;//对应位置乘上-1
        }
    }

    //System.out.println(Arrays.toString(nums));//确定还原了

    return res;
}

300. 最长递增子序列

300. 最长递增子序列

经典老题,先用dp试试

public int lengthOfLIS(int[] nums) {
    int[] dp = new int[nums.length];
    dp[0]=1;
    int max = 1;

    for (int i = 1; i < nums.length; i++) {
        int maxv = 0;
        for(int j=i-1;j>=0;j--){
            if(nums[j]<nums[i]) maxv = Math.max(maxv,dp[j]);
        }
        dp[i] = maxv+1;

        max = Math.max(max,dp[i]);
    }
    return max;
}

不考虑效率的dp O(n^2) O(1)

下面看题解,来优化效率了

  • 动态规划 + 二分查找 O(nlogn)O(1)
    优化效率,维护一个tail序列数组,新元素插入数组,插入也是有讲究的,除非比tail末尾元素要大,就新加入 , 否则二分查找就是替换对应位置的元素,不要怕替换后位置不对,因为,只要没有将tail数组长度边长,就不会影响答案正确性

在这里插入图片描述
678129346
6
67
678
178
128
1289 (没问题 这时6789确实长度是4)
1239
1234
12346

public int lengthOfLIS(int[] nums) {
    int[] tail = new int[nums.length];
    int res=0;
    for (int i = 0; i < nums.length; i++) {
        int l=0,r=res;
        // 二分找插入位置(直接覆盖 不真的插入 除非是最后一个)   其实就是找到第一个大于nums[i]的元素 替换掉他即可
        while (l<r){
            int m=(l+r)/2;
            if(nums[i]>tail[m]) l=m+1; //r初始值是res本来就是最右边的空闲下标
            else r=m;//可以覆盖
        }
        if(r==res) res++;
        tail[r] = nums[i];
        //System.out.println(Arrays.toString(tail));
    }
    return res;
}

309. 最佳买卖股票时机含冷冻期

309. 最佳买卖股票时机含冷冻期

这题的dp有3个,超级难想啊亲,直接看题解吧:

官方题解看得云里雾里
再看看大佬题解,进一步解析了一番,清晰多了

/*
0.不持股且当天没卖出,定义其最大收益dp[i][0];
1.持股,定义其最大收益dp[i][1];
2.不持股且当天卖出了,定义其最大收益dp[i][2];
* */
public int maxProfit(int[] prices) {
    int n = prices.length;
    int[][] dp = new int[n][3];

    //dp[0][0] = 0;//默认是0
    dp[0][1] = -prices[0];
    //dp[0][2] = 0; // 买入又卖出 当天为0  //默认是0

    for (int i = 1; i < n; i++) {
        dp[i][0] = Math.max(dp[i-1][0],dp[i-1][2]);//不持股且当天没卖出 =》  i-1天也不持股2种状态取大者
        dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);//持股: 昨天就持股  或者  昨天不持股,今天刚买入
        dp[i][2] = dp[i-1][1] + prices[i]; //昨天持股 今天卖出
    }

    return Math.max(dp[n-1][0],dp[n-1][2]);//持股的不用管 持股没用 没时间卖了都
}

322. 零钱兑换

322. 零钱兑换

先直接dfs,枚举,小恩小惠的剪枝也不行,仍然超时

只能类似之前dp那样,用一个数组记录下已经dfs过的状态,然后就可以大量减枝了,每个amount状态真的就只计算一次
也就是:记忆化搜索

// 不需要排序 意义不大
public int coinChange(int[] coins, int amount) {
    ans = new int[amount+1];//0下标不用
    int res = dfs(coins, amount);
    if(res==Integer.MAX_VALUE) return -1;
    else return res;
}
// 记忆化搜索
int[] ans; // ans[i]表示 总金额为i时最少硬币数
private int dfs(int[] coins, int amount) {
    if(amount<0) return Integer.MAX_VALUE;
    if(amount==0) return 0;//重要边界

    if(ans[amount]!=0) return ans[amount];//大量减枝  

    // 否则就慢慢算
    int min = Integer.MAX_VALUE;
    for (int i = 0; i < coins.length; i++) {
        int res = dfs(coins, amount - coins[i]);
        if(res<min) min=res+1;
    }
    ans[amount] = min;//记录下中间状态  记忆化搜索
    return ans[amount];
}

写完dfs再写dp就简单多了
dp速度也确实快一点

// 不需要排序 意义不大
public int coinChange(int[] coins, int amount) {
    int[] dp = new int[amount + 1];
    int MAX_VALUE = amount + 1;//最大只能是 amount,也即是 全部是1元的硬币    不需要用到Integer.MAX_VALUE
    Arrays.fill(dp, MAX_VALUE);
    dp[0] = 0;
    // 所有总金额 依次计算状态
    for (int i = 1; i <= amount; i++) {
        // 遍历所有硬币
        for (int j = 0; j < coins.length; j++) {
            if (coins[j] <= i) {
                dp[i] = Math.min(dp[i - coins[j]] + 1, dp[i]);
            }
        }
    }
    return dp[amount] >= MAX_VALUE ? -1 : dp[amount];
}

337. 打家劫舍 III

337. 打家劫舍 III

第一思路: 简单来看 孩子被偷了 父亲就不能再被偷了 所以后序遍历试试

想到了后续遍历,但是没有想到需要同时维护两个值
想象成树型dp就好多了。难一点的dp本来就要多种状态同时转移

这个题解非常好

  • 先看一个差一点的题解
// 记忆 或者说 缓存  (不缓存会超时)
HashMap<TreeNode, Integer> memo = new HashMap<>();

// 4 个孙子偷的钱 + 爷爷的钱 VS 两个儿子偷的钱 哪个组合钱多
public int rob(TreeNode root) {
    if (root == null) return 0;

    if(memo.get(root) != null) return memo.get(root);

    int money = root.val;//爷孙
    if (root.left != null) {
        money += rob(root.left.left) + rob(root.left.right);
    }
    if (root.right != null) {
        money += rob(root.right.left) + rob(root.right.right);
    }
    int dad = rob(root.left) + rob(root.right);//父  (也要递归获取)

    // 记忆
    int max = Math.max(money, dad);
    memo.put(root, max);

    return max;
}
  • 树型dp
public int rob(TreeNode root) {
    int[] ans = rob2(root);
    return Math.max(ans[0],ans[1]);
}

public int[] rob2(TreeNode root) {
    if(root==null) return new int[]{0,0};

    int[] left = rob2(root.left);
    int[] right = rob2(root.right);//一次后续遍历  不会重复遍历节点  (相当于一次遍历同时维护两个dp数组)

    int[] ans = new int[2];
    ans[1] = root.val + left[0] + right[0];//本节点偷了  两个孩子都不能偷了
    ans[0] = Math.max(left[0], left[1]) + Math.max(right[0],right[1]);// 本结点不偷 孩子偷不偷都无妨 取最大

    return ans;
}

惊人:0ms

树的动态规划,得有这个意识了!!!!

  • 最最后批判一下我自己的垃圾写法

直接用先序遍历,需要缓存
先序遍历每个节点有两种状态,缓存也得缓存2份
实在是太拉了

// 简单来看 孩子被偷了 父亲就不能再被偷了  所以后序遍历试试
// 不行,还是先序遍历吧 因为父亲可以选择偷还是不偷  然后 可以选择偷不偷孩子
public int rob(TreeNode root) {
    return Math.max(rob(root, true), rob(root, false));
}

// 超时了 加一个缓存吧
HashMap<TreeNode, Integer> memo1 = new HashMap<>();
HashMap<TreeNode, Integer> memo2 = new HashMap<>();

// boolean canStolen 记录下当前结点是否可以偷
public int rob(TreeNode root, boolean canStolen) {
    if (root == null) return 0;

    if (canStolen && memo1.get(root) != null) return memo1.get(root);
    if (!canStolen && memo2.get(root) != null) return memo2.get(root);

    //System.out.println(canStolen+" "+root.val);

    int sum1 = 0, sum2 = 0;
    if (canStolen) {//可以偷本结点 (说明父亲没被偷)
        sum1 += root.val;
        sum1 += rob(root.left, false);//本结点偷了 子节点 就不可以偷了
        sum1 += rob(root.right, false);
    }
    //让然不管可不可以偷本结点 我都可以选择不偷
    sum2 += rob(root.left, true);//子结点就可以偷了
    sum2 += rob(root.right, true);

    int ans = Math.max(sum1, sum2);
    if (canStolen) memo1.put(root, ans);
    if (!canStolen) memo2.put(root, ans);
    // 每个结点有两种状态 (node,true) (node,false) 缓存一种状态也不行

    return ans;
}

338. 比特位计数

338. 比特位计数

  • 我的写法,比较烂
public int[] countBits(int n) {
    int[] ans = new int[n + 1];
    for (int i = 0; i <= n; i++) {
        int k = 0;
        int num = i;
        while (num != 0) {
            k += num % 2;
            num /= 2;
            if(ans[num]!=0){
                k += ans[num];
                break;
            }
        }
        ans[i] = k;
    }
    return ans;
}
  • 没想到,java有现成的API
public int[] countBits(int n) {
    int[] ans = new int[n+1];
    for (int i = 0; i <= n; i++) {
        ans[i] = Integer.bitCount(i);
    }
    return ans;
}

看一下源码:

@IntrinsicCandidate
public static int bitCount(int i) {
    // HD, Figure 5-2
    i = i - ((i >>> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
    i = (i + (i >>> 4)) & 0x0f0f0f0f;
    i = i + (i >>> 8);
    i = i + (i >>> 16);
    return i & 0x3f;
}

看不懂

  • 题解好多种dp,个人觉得最牛的dp还是这一版
public int[] countBits(int n) {
    int[] ans = new int[n+1];
    for (int i = 1; i <= n; i++) {
        if(i%2==0) ans[i] = ans[i/2];//左移一位 末尾添0  不增加1的个数
        else ans[i] = ans[i-1] + 1; //上一个奇数 末尾+1
    }//正儿八经的O(n)形式的dp
    return ans;
}

更进一步,奇偶都不需要要讨论了,直接右移,转换为已知,末尾是1就加上

public int[] countBits(int n) {
    int[] ans = new int[n+1];
    for (int i = 1; i <= n; i++) {
        ans[i] = ans[i>>1] + (i&1); //右移动+末位加上舍弃的数字
    }
    return ans;
}

347. 前 K 个高频元素

347. 前 K 个高频元素

  • 先用Hash+堆排序 直接套一波。 时间O(nlogn) 空间O(distinct(nums))
public int[] topKFrequent(int[] nums, int k) {
    HashMap<Integer, Integer> map = new HashMap<>();
    for (int num : nums) {
        Integer v = map.getOrDefault(num, 0);
        map.put(num,v+1);
    }

    // 根据value(也就是出现频率)  降序排序
    PriorityQueue<Map.Entry<Integer, Integer>> pq = new PriorityQueue<>((a,b)->b.getValue()-a.getValue());

    for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
        pq.offer(entry);//优先队列自动堆排序
    }

    int[] ans = new int[k];
    for (int i = 0; i < ans.length; i++) {
        ans[i] = pq.poll().getKey();
    }

    return ans;
}

但是题目要求的是由于O(nlogn), 这个时候想想, 堆不正适合找最大(或者最小)的k个数么,选择性地将k个元素放入堆中即可
最大的k个数,也就是建立小顶堆,先直接入堆个,堆顶维护最小值,大于堆顶替换堆顶,小于直接舍弃

public int[] topKFrequent(int[] nums, int k) {
   HashMap<Integer, Integer> map = new HashMap<>();
   for (int num : nums) {
       Integer v = map.getOrDefault(num, 0);
       map.put(num,v+1);
   }

   // 小根堆 只要k个就行了
   PriorityQueue<Map.Entry<Integer, Integer>> pq = new PriorityQueue<>((a,b)->a.getValue()-b.getValue());

   for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
       if(pq.size()<k) pq.offer(entry);//优先队列自动堆排序  默认小根堆 堆顶最小
       else {
           if(entry.getValue()>pq.peek().getValue()){
               pq.poll();//舍弃堆顶
               pq.offer(entry);// 重新纳入堆顶
           }
       }
   }

   int[] ans = new int[k];
   for (int i = 0; i < ans.length; i++) {
       ans[i] = pq.poll().getKey();
   }

   return ans;
}

感觉测试用例有问题,效率并没有怎么提升。代码还复杂了不少

394. 字符串解码

394. 字符串解码

括号匹配,还像极了表达式求值,那不如就,用栈吧

注意字符串最左边是下标0 入栈时别弄反了

用栈还真行

// 既然是括号匹配 那就先用栈吧
public String decodeString(String s) {
    Stack<Character> stack = new Stack<>();
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if(c!=']'){
            stack.push(c);
        }else {
            // 中括号内的字符串-》str
            char tc;
            String str = "";
            while ((tc=stack.pop())!='[') {//最后多pop的'[' 正好丢弃
                str = tc+str;//注意字符串最左边是下标0
            }

            // 中括号前前面的整数-> ts->k
            String ts = "";
            while (!stack.isEmpty()&&Character.isDigit(stack.peek())){//不能多pop了 改用peek
                ts = stack.pop() + ts;
            }
            int k = Integer.parseInt(ts);
            //System.out.println(str+" "+ k);//bc 2

            // 栈内 压入k个str
            while (k-->0){
                for (int j = 0; j < str.length(); j++) {
                    stack.push(str.charAt(j));
                }
            }
        }
    }
    String ans = "";
    while (!stack.isEmpty()){
        ans = stack.pop() + ans;
    }
    return ans;
}

语法层面整理一下,效率竟然升高了,很迷惑

// 既然是括号匹配 那就先用栈吧
public String decodeString(String s) {
    Stack<Character> stack = new Stack<>();
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (c != ']') stack.push(c);
        else {
            String str = "";
            while ((stack.peek()) != '[') str = stack.pop() + str;
            stack.pop();//'['

            String ts = "";
            while (!stack.isEmpty() && Character.isDigit(stack.peek())) ts = stack.pop() + ts;
            int k = Integer.parseInt(ts);

            while (k-- > 0)
                for (int j = 0; j < str.length(); j++) stack.push(str.charAt(j));
        }
    }
    String ans = "";
    while (!stack.isEmpty()) ans = stack.pop() + ans;
    return ans;
}

题解大思路跟我差不多,细节上有所不同,题解用的不定长数组LinkedList模拟的栈,遍历时要快一点而已,综合要快2ms

406. 根据身高重建队列

406. 根据身高重建队列

官解太麻烦了,有个简单解法

在这里插入图片描述

public int[][] reconstructQueue(int[][] people) {
    LinkedList<int[]> list = new LinkedList<>();
    Arrays.sort(people, new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) {
            if(o1[0]!=o2[0]) return o2[0]-o1[0];
            else return o1[1]-o2[1];
        }
    });
    for (int[] person : people) {
        list.add(person[1],person);
    }
    return list.toArray(new int[0][]);
}

416. 分割等和子集

416. 分割等和子集

全部样例都通过,然后还报超时,也是没谁了

// 总和是定的 是否加起来=1半就行了
public boolean canPartition(int[] nums) {
    int target = Arrays.stream(nums).sum();
    if(target%2==0) target /=2;
    else return false;
    dfs(nums,0,target);//接下来就是看数组种是否有某个子集的和为target了 转化成了之前的dfs问题
    return flag;
}

boolean flag = false;
HashMap<String, Boolean> map = new HashMap<String, Boolean>();//减枝
boolean dfs(int[] nums,int n,int target){
    if(target<0||n>=nums.length) return false;
    if(target==0||flag) return true;

    if(map.containsKey(n+"-"+target)) return map.get(n+"-"+target);

    // 选或者不选2种情况
    boolean b = dfs(nums,n+1,target) || dfs(nums,n+1,target-nums[n]);
    map.put(n+"-"+target,b);
    if(b) flag = b;
    return b;
}

只能转换成非递归形式,也就是dp来做了

仔细想想,不就是0-1背包吗?这里没有权重限制,只要拿或者不拿,能偷到恰好target的物品即可。
先用二维数组写,再优化为一维滚动数组

注意滚动数组这里得倒过来遍历 否则前面的会覆盖后面的

public boolean canPartition(int[] nums) {
    int target = Arrays.stream(nums).sum();
    if(target%2==0) target /=2;
    else return false;

    boolean[] dp = new boolean[target+1];
    dp[0] = true;
    if(nums[0]<=target) dp[nums[0]] = true;//也是边界  // 第0次的dp
    // 其他默认false

    for(int i = 1; i < nums.length; i++){
        for (int j = target; j >= 1; j--) {//注意滚动数组这里得倒过来遍历 否则前面的会覆盖后面的
            if(j>=nums[i]){//第i个不能拿
                dp[j] = dp[j]|dp[j-nums[i]];
            }
        }
    }
    return dp[target];
}

437. 路径总和 III

437. 路径总和 III

虽然之前在剑指里刷过,但是二刷还是不会,细节,还有反复多次,真的很重要。
时间不够时,就要特别注重方法

最简单的思路:两次先序遍历,第一次确定起点,第二次dfs找路径,就这么简单
每次定死一个起点,绝对不会有重复的 起点到每个结点都只算了一次
if(targetSum-root.val==0) 先减了root.val才是访问了该结点,才是先序。否则if(targetSum==0) 并不是先序,根本就是错的

还有就是要注意一个坑爹的测试用例:[1000000000,1000000000,null,294967296,null,1000000000,null,1000000000,null,1000000000]
Integer溢出之后正好变成了0,所以dfs里的targetSum要改成Long类型

public int pathSum(TreeNode root, int targetSum) {
    if(root==null) return 0;
    dfs(root, (long) targetSum);//先序遍历确定起点
    pathSum(root.left,targetSum);
    pathSum(root.right,targetSum);
    return count;
}

int count=0;
public void dfs(TreeNode root, Long targetSum) {
    if (root == null) return;
    if(targetSum-root.val==0) count++;//-root.val才表示先序访问了这个结点
    //先序遍历 一定得先减     所以千万注意这里是targetSum-root.val==0而不是targetSum==0否则就不是先序遍历了
    dfs(root.left, targetSum-root.val);
    dfs(root.right, targetSum-root.val);
}
  • 用map记录前缀和,飞快

在这里插入图片描述
前缀和,太牛了~

public int pathSum(TreeNode root, int targetSum) {
    HashMap<Long, Integer> prex = new HashMap<>();
    prex.put(0L,1);//很重要 否则起点正好是根root的路径就会被遗漏
    dfs(root, (long) targetSum,0l,prex);
    return count;
}

int count=0;
public void dfs(TreeNode root, Long targetSum, Long curr, HashMap<Long,Integer> prex) {
    if (root == null) return;

    curr += root.val;

    count += prex.getOrDefault(curr-targetSum,0);
    prex.put(curr,prex.getOrDefault(curr,0)+1);

    dfs(root.left, targetSum,curr,prex);
    dfs(root.right, targetSum,curr,prex);

    // 退栈时清除当前结点
    prex.put(curr,prex.getOrDefault(curr,0)-1);
}

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

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

自己尝试滑动窗口,艰难地通过了
类似kmp, 但是这里引出了距离的概念,2个串之间的距离,为 p-s.sub 的差集长度

public List<Integer> findAnagrams(String s, String p) {
    ArrayList<Integer> ans = new ArrayList<>();
    if(s.length()<p.length()) return ans;

    HashMap<Character, Integer> mapp = new HashMap<>();
    HashMap<Character, Integer> mapsub = new HashMap<>();
    int d = p.length();
    int l=0,r=d-1;

    // 死Hash
    for (int i = 0; i < d; i++) {
        mapp.put(p.charAt(i),mapp.getOrDefault(p.charAt(i),0)+1);
    }

    // 动态Hash
    for (int i = l; i <= r; i++) {
        mapsub.put(s.charAt(i),mapsub.getOrDefault(s.charAt(i),0)+1);
    }

    while (l<=r&&r<s.length()){
        int t = distance(mapp,mapsub);
        if(t==0) {
            ans.add(l);
            //System.out.println(s.substring(l,r+1));
            mapsub.put(s.charAt(l),mapsub.get(s.charAt(l))-1);
            l++;r++;
            if(r>=s.length()) break;
            mapsub.put(s.charAt(r),mapsub.getOrDefault(s.charAt(r),0)+1);
        }
        else {
            while (t-->0){
                mapsub.put(s.charAt(l),mapsub.get(s.charAt(l))-1);
                l++;
                r++;
                if(r>=s.length()) break;
                mapsub.put(s.charAt(r),mapsub.getOrDefault(s.charAt(r),0)+1);
            }
        }
    }
    return ans;
}

public int distance(HashMap<Character, Integer> map1,HashMap<Character, Integer> map2){
    int dis = 0;
    for (Character c : map1.keySet()) {
        int n1 = map1.get(c);
        int n2 = map2.getOrDefault(c,0);
        dis += n2>=n1?0:n1-n2;
    }
    return dis;
}

语法层面优化一下代码:

优化1:

public void put(HashMap<Character, Integer> map,String s){
    for (char c : s.toCharArray()) {
        put(map, c,1);
    }
}

public void put(HashMap<Character, Integer> map,char c,int v){//原来基础上+v
    map.put(c, map.getOrDefault(c,0)+v);
}

public List<Integer> findAnagrams(String s, String p) {
    ArrayList<Integer> ans = new ArrayList<>();
    if (s.length() < p.length()) return ans;

    HashMap<Character, Integer> mapp = new HashMap<>();
    HashMap<Character, Integer> mapsub = new HashMap<>();
    int l = 0, r = p.length() - 1;

    // 死Hash
    put(mapp,p);
    // 动态Hash
    put(mapsub,s.substring(l,r+1));

    while (l <= r && r < s.length()) {
        int t = distance(mapp, mapsub);
        if (t == 0) {
            ans.add(l);
            put(mapsub,s.charAt(l++),-1);
            if (++r>= s.length()) break;
            put(mapsub,s.charAt(r),1);
        } else {
            while (t-- > 0) {
                put(mapsub,s.charAt(l++),-1);
                if (++r >= s.length()) break;
                put(mapsub,s.charAt(r),1);
            }
        }
    }
    return ans;
}

public int distance(HashMap<Character, Integer> map1, HashMap<Character, Integer> map2) {
    int dis = 0;
    for (Character c : map1.keySet()) {
        int n1 = map1.get(c);
        int n2 = map2.getOrDefault(c, 0);
        dis += n2 >= n1 ? 0 : n1 - n2;
    }
    return dis;
}

优化2:只会出现小写字母,直接Hash = new int[26] 不就行了

public List<Integer> findAnagrams(String s, String p) {
    ArrayList<Integer> ans = new ArrayList<>();
    if (s.length() < p.length()) return ans;

    int[] hashP = new int[26];
    int[] hashS = new int[26];
    int l = 0, r = p.length();//左闭右开

    for (char c : p.toCharArray()) {
        hashP[c - 'a']++;
    }
    for (char c : s.substring(0, r).toCharArray()) {
        hashS[c - 'a']++;
    }

    while (l < r && r <= s.length()) {//[l,r) 左闭右开
        // 计算距离
        int dis = 0;
        for (int i = 0; i < 26; i++) {
            if (hashP[i] > hashS[i]) dis += hashP[i] - hashS[i];
        }
        if (dis == 0) {
            ans.add(l);
            hashS[s.charAt(l++)-'a']--;
            if (r >= s.length()) break;
            hashS[s.charAt(r++)-'a']++;
        } else {
            while (dis-- > 0) {
                hashS[s.charAt(l++)-'a']--;
                if (r >= s.length()) break;
                hashS[s.charAt(r++)-'a']++;
            }
        }
    }
    return ans;
}

快了不是一点点呀,简直了
在这里插入图片描述

思路完全正确,和题解几乎一样,但是其实还可以进一步优化的
看了题解 进一步优化,直接维护一个differ数组即可 (计算differ模块就从O(26)变O(1),也还行)。先不写了

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

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

老规矩,先空间上暴力求解一下,时间上几乎无敌,空间就很拉

public List<Integer> findDisappearedNumbers(int[] nums) {
    ArrayList<Integer> ans = new ArrayList<>();
    int[] hash = new int[nums.length + 1];
    for (int num : nums)  hash[num]=1;
    for (int i = 1; i < hash.length; i++) if(hash[i] == 0) ans.add(i);
    return ans;
}

接下来再考虑如何优化空间

public List<Integer> findDisappearedNumbers(int[] nums) {
    ArrayList<Integer> ans = new ArrayList<>();
    int n = nums.length;
    // 修改原数组
    for (int i = 0; i < n; i++) {
        nums[nums[i]%n] += n;
    }
    for (int i = 0; i < n; i++) {
        if(nums[i]<=n) ans.add(i==0?n:i);//<=n也是不存在  严格>n才是存在  因为本来值的范围在[1,n]
    }
    return ans;
}

看起来节省了空间,但是实际执行,效果却并不明显

461. 汉明距离

461. 汉明距离

异或之后的那个数中1的个数就是,于是转换为 338.比特位计数

java自带的API可以O(1)的复杂度求bitCount

public int hammingDistance(int x, int y) {
    return Integer.bitCount(x^y);//API O(1)的解法
}

494. 目标和

494. 目标和

  • 先dfs暴力搜索一下,能通过即可
public int findTargetSumWays(int[] nums, int target) {
    dfs(nums,0,target);
    return count;
}
int count = 0;
void dfs(int[] nums,int n,int target){
    if(target==0&&n==nums.length) count++;
    if(n>=nums.length) return;
    dfs(nums,n+1,target-nums[n]);//-号
    dfs(nums,n+1,target+nums[n]);//+号
}

可以简化一下:

public int findTargetSumWays(int[] nums, int target) {
    return dfs(nums,0,target);
}
int dfs(int[] nums,int n,int target){
    if(target==0&&n==nums.length) return 1;
    if(n>=nums.length) return 0;
    return dfs(nums,n+1,target-nums[n])+dfs(nums,n+1,target+nums[n]);
}

dfs优化,肯定得写dp了,感觉好麻烦啊
看题解吧

哎呀,原来就是背包问题啊,这么一想,也不难嘛

不过细节上还是很烦人的,主要是平移操作(下标不能为负数啊)

public int findTargetSumWays(int[] nums, int target) {
    // 注意 中间的target可以出现负数 但是下标不能是负数啊 只得平移一下了
    // 用sum平移: 0 <= nums[i] <= 1000 都是正的用sum平移正好
    // 本来  -sum<=j<=sum  ==现在==>  0<=j<=2*sum
    int sum = Arrays.stream(nums).sum();
    if (Math.abs(target) > sum) return 0;//绝对值超过sum绝对无解 不需要考虑

    //dp[i][j] nums[0~i]值为j的表达式的个数 (这样容纳了所有dfs的情况 还不用递归)
    int N = 2 * sum + 1; //下标 [0~2*sum]
    int[][] dp = new int[nums.length][N];

    // 初始化边界很重要 //下面有i-1 所以得从i=1开始优化  0就单独作为边界初始化一下吧
    // i==0 只有两种状态为1   dp[0][nums[0]]=1和dp[0][-nums[0]]=1
    //             平移后就是dp[0][sum+nums[0]]=1和dp[0][sum-nums[0]]=1
    dp[0][sum + nums[0]]++;
    dp[0][sum - nums[0]]++;//++是为了防止 nums[0]==0   确实+nums[0]和-nums[0] 两个表达式值都为j

    for (int i = 1; i < nums.length; i++) {
        for (int j = 0; j < N; j++) {
            // dp[0][0~2*sum]都以及初始化好了 下面直接状态转移就行了
            int a = j - nums[i] >= 0 ? j - nums[i] : 0;
            int b = j + nums[i] < N ? j + nums[i] : 0;
            dp[i][j] = dp[i - 1][a] + dp[i - 1][b];
        }
    }
    return dp[nums.length - 1][sum+target];//注意目标值平移target
}

这还没法优化成滚动数组,因为j+nums[i],j-nums[i]前后都要用 就先这样吧

538. 把二叉搜索树转换为累加树

538. 把二叉搜索树转换为累加树

感觉有点简单啊

  • 典型的"后序"遍历 BST中序是升序 所以这里应该是逆中序 也就是 LNR->RNL
int pre = 0;
public TreeNode convertBST(TreeNode root) {
    if(root==null) return null;
    convertBST(root.right);
    root.val += pre;
    pre = root.val;
    convertBST(root.left);
    return root;
}

据说: Morris 遍历 可以不用递归实现树的中序遍历,也就节省了空间

非递归实现中序遍历,留待后期慢慢做工作吧

543. 二叉树的直径

543. 二叉树的直径

  • 个人见解: 每个结点的左右子树深度之和取最大值
int max = 0;
public int diameterOfBinaryTree(TreeNode root) {
    if(root==null) return 0;
    int path = height(root.left)+height(root.right);
    max = Math.max(max, path);
    diameterOfBinaryTree(root.left);
    diameterOfBinaryTree(root.right);
    return max;
}

HashMap<TreeNode, Integer> map = new HashMap<>();//大量剪枝
int height(TreeNode root) {
    if (root == null) return 0;
    if(map.containsKey(root)) return map.get(root);
    int high =  Math.max(height(root.left), height(root.right)) + 1;
    map.put(root,high);
    return high;
}

通过了,2ms,但是显然还有更优的解法

果然可以优化: 求height的过程中就可以 维护max了

public int diameterOfBinaryTree(TreeNode root) {
    height(root);
    return max;
}
int max = 0;
int height(TreeNode root) {
    if (root == null) return 0;
    int left = height(root.left);
    int right = height(root.right);
    max = Math.max(max, left + right);//求height的过程中就可以 维护max呀
    return Math.max(left, right) + 1;
}




美团: 排列变成有序,每次只能拿两个数,最大的放最后,小的放最前,重复操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/856898.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java定时算法实现与应用(最小堆、时间轮)

文章目录 一、定时算法 概述二、最小堆算法1、概述2、Java实现最小堆算法&#xff08;1&#xff09;Timer使用&#xff08;2&#xff09;源码分析 3、应用 三、 时间轮算法1、概述2、Java实现时间轮算法 一、定时算法 概述 系统或者项目中难免会遇到各种需要自动去执行的任务&…

免费小程序商城搭建之b2b2c o2o 多商家入驻商城 直播带货商城 电子商务b2b2c o2o 多商家入驻商城 直播带货商城 电子商务 bbc

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端…

视频音频怎么提取成MP3?教你几种简单提取方法

提取视频音频并将其转换为MP3格式有很多好处。首先&#xff0c;MP3格式是一种广泛支持的音频格式&#xff0c;可以在几乎所有设备上播放&#xff0c;包括计算机、手机和音乐播放器。这意味着您可以在任何设备上随时随地享受音频内容&#xff0c;而不必担心格式兼容性问题。那么…

Win11中使用pip或者Cython报错 —— error: Microsoft Visual C++ 14.0 is required.

第一步&#xff1a;下载Visual Studio 2019 下载地址&#xff1a; https://learn.microsoft.com/zh-cn/visualstudio/releases/2019/release-notes 第二步&#xff1a;安装组件 选择单个组件&#xff0c;勾选以下两个组件 其他错误&#xff1a; 无法打开文件“python37.li…

触控触感方案原厂18按键触摸芯片电路图

VK3618I具有18个触摸按键&#xff0c;可用来检测外部触摸按键上人手的触摸动作。该芯片具有较 高的集成度&#xff0c;仅需极少的外部组件便可实现触摸按键的检测。 提供了2组I2C输出功能&#xff0c;1个INT中断输出脚&#xff0c;2组I2C脚和INT可并联&#xff0c;每组单键输出…

【腾讯云 Cloud Studio 实战训练营】快速构建React完成点餐H5页面

一&#xff0c;前言 1.1 相关链接 官网地址&#xff1a;Cloud Studio 官方文档地址&#xff1a;Cloud Studio&#xff08;云端 IDE&#xff09;简介 | Cloud Studio 1.2 Cloud Studio&#xff08;云端 IDE&#xff09;简介 Cloud Studio 是基于浏览器的集成式开发环境&#…

山景DSP芯片可烧录AP8224C2音频处理器方案

AP8224C2高性能32位音频应用处理器AP82系列音频处理器是面向音频应用领域设计的新一代SoC平台产品&#xff0c;适用于传统音响系统、新兴的蓝牙或Wifi 无线音频产品、Sound Bar 和调音台等市场。该处理器在总体架构和系统组成上&#xff0c;充分考虑了音频领域的特点&#xff0…

一个月学通Python(三十四):使用Selenium模拟人工操作及获取网页内容

专栏介绍 结合自身经验和内部资料总结的Python教程&#xff0c;每天3-5章&#xff0c;最短1个月就能全方位的完成Python的学习并进行实战开发&#xff0c;学完了定能成为大佬&#xff01;加油吧&#xff01;卷起来&#xff01; 全部文章请访问专栏&#xff1a;《Python全栈教…

【cs61b】学习笔记day2

历史文章目录 【cs61b】学习笔记day1 文章目录 历史文章目录List两个小问题bits声明一个变量引用类型方框和指针表示法数组的实例化链表 SLList List 两个小问题 思考下面两个代码分别输出什么 Walrus a new Walrus(1000, 8.3); Walrus b; b a; b.weight 5; System.out.…

并发编程面试题1

并发编程面试题1 一、原子性高频问题&#xff1a; 1.1 Java中如何实现线程安全? 多线程操作共享数据出现的问题。 锁&#xff1a; 悲观锁&#xff1a;synchronized&#xff0c;lock乐观锁&#xff1a;CAS 可以根据业务情况&#xff0c;选择ThreadLocal&#xff0c;让每个…

2023最新版Anaconda下载安装教程(非常详细)从零基础入门到精通,看完这一篇就够了

1. 前言 小编的电脑是win10系统的&#xff0c;这里以win10系统安装Anaconda为例&#xff0c;其他的系统安装过程类似&#xff0c;可以照猫画虎&#xff0c;下面请看具体的安装过程。 2. 下载软件 1、首先去官网上进行下载软件&#xff0c;下载地址&#xff1a; https://docs…

php获取随机订单号(封装函数)

作为一个开发人员&#xff0c;生成订单时常常需要获取一段随机码来表示订单号&#xff0c;并且订单号一般包含的特定的时间日期等信息&#xff0c;临时现写一个比较浪费时间&#xff0c;这里有一个封装好的生成随机订单号的函数&#xff0c;需要时直接调用即可。 代码如下&…

【腾讯云 Cloud Studio 实战训练营】一个新的趋势已来

写在前面&#xff1a;博主是一只经过实战开发历练后投身培训事业的“小山猪”&#xff0c;昵称取自动画片《狮子王》中的“彭彭”&#xff0c;总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域&#xff0c;如今终有小成…

Datahub稳定版本0.10.4安装指南(独孤风版本)

大家好&#xff0c;我是独孤风&#xff0c;大数据流动的作者。 曾几何时&#xff0c;我在第一次安装JDK环境的时候也遇到了不小的麻烦&#xff0c;当时还有朋友就因为这个环境问题觉得自己根本不是编程的料&#xff0c;选择了放弃。当时有个段子说&#xff0c;“如果不是JDK环境…

生物学家呼吁:基因组测序是从大流行病中快速获得信息的最重要方法之一

生物学家Jason Ladner和Jason Sahl于2023年8月1日发表在《PLOS Biology》&#xff08;IF20229.8&#xff09;的一篇文章中主张&#xff0c;持续发展基因组测序是能从大流行病中快速获得信息的最重要方法之一。基因组测序对全球应对COVID-19产生了巨大影响&#xff0c;随着更多研…

与这个夏天的快乐与不快乐,都挥手告别吧!

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 伊姐 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦Midjourney 产品统筹 / bobo 场地支持 / 声湃轩天津录音间 2023这个夏天&#xff0c; 地震、暴热、洪水……呼的一下&#xff0c; 密集发生的天灾让人揪心&#xff0c…

Unity-Shader-高亮Highlight

常用Shader-高亮&#xff0c;可动态调整高亮颜色、高亮强度范围/等级、高亮闪烁速度、高亮状态 Shader "CustomShader/Highlight" {Properties{_Color("Color", Color) (0.9044118,0.6640914,0.03325041,0)_Albedo("Albedo", 2D) "white…

vue自定义密码输入框解决浏览器自动填充密码的问题

浏览器对于type"password"的输入框会自动填充密码&#xff0c;但有时出于安全或者其他原因&#xff0c;我们不希望浏览器记住并自动填充密码。通过网上查到的一些解决方案&#xff0c;可以总结出以下几种解决方案(主要用edge浏览器进行测试)&#xff1a; 通过autoco…

玩一玩通义千问Qwen开源版,Win11 RTX3060本地安装记录!

大概在两天前&#xff0c;阿里做了一件大事儿。 就是开源了一个低配版的通义千问模型--通义千问-7B-Chat。 这应该是国内第一个大厂开源的大语言模型吧。 虽然是低配版&#xff0c;但是在各类测试里面都非常能打。 官方介绍&#xff1a; Qwen-7B是基于Transformer的大语言模…

[JavaScript游戏开发] Q版地图上让英雄、地图都动起来

系列文章目录 第一章 2D二维地图绘制、人物移动、障碍检测 第二章 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示) 第三章 绘制冰宫宝藏地图、人物鼠标点击移动、障碍检测 第四章 绘制Q版地图、键盘上下左右地图场景切换 第五章 Q版地图上让英雄、地图都动起来…