月度归档: 2022年3月

LeetCode刷题【剑指 Offer 35】复杂链表的复制

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null

 

示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

 

提示:

  • -10000 <= Node.val <= 10000
  • Node.random 为空(null)或指向链表中的节点。
  • 节点数目不超过 1000 。

 

注意:本题与主站 138 题相同:https://leetcode-cn.com/problems/copy-list-with-random-pointer/

 

Related Topics
  • 哈希表
  • 链表

  • 👍 451
  • 👎 0
  • 【哈希表】java,配合一次遍历

    有点绕,和官方题解类似。

    • 构建新链表,和一个旧到新节点的映射关系的nodeMap,key为旧节点,value为对应的新节点。
    • 当我们在遍历旧链表的同时,构建新链表
    • 果旧链表的nextrandom指向的旧节点和新节点的关系在nodeMap不存在,则构建一个新节点,并把对应的nextrandom指向到对应的新节点
    • 如果映射关系在nodeMap中存在,则直接指向对应的新节点
    • 再把新旧链表的遍历指针往后移动一位

    代码

    class Solution {
        public Node copyRandomList(Node head) {
            if (head==null){
                return null;
            }
            Node newHead = new Node(head.val);
            //key为旧的,value为新的
            HashMap<Node, Node> nodeMap = new HashMap<>();
            nodeMap.put(head,newHead);
            Node newCur = newHead;
            Node cur = head;
            while (cur!=null){
                if (!nodeMap.containsKey(cur.next)){
                    nodeMap.put(cur.next, cur.next==null ? null : new Node(cur.next.val));
                }
                if (!nodeMap.containsKey(cur.random)){
                    nodeMap.put(cur.random, cur.random==null ? null : new Node(cur.random.val));
                }
    
                newCur.next = nodeMap.get(cur.next);
                newCur.random = nodeMap.get(cur.random);
                cur = cur.next;
                newCur = newCur.next;
            }
    
    
            return newHead;
        }
    }

    LeetCode刷题【211】添加与搜索单词 – 数据结构设计

    请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。

    实现词典类 WordDictionary

    • WordDictionary() 初始化词典对象
    • void addWord(word)word 添加到数据结构中,之后可以对它进行匹配
    • bool search(word) 如果数据结构中存在字符串与 word 匹配,则返回 true ;否则,返回  falseword 中可能包含一些 '.' ,每个 . 都可以表示任何一个字母。

     

    示例:

    输入:
    ["WordDictionary","addWord","addWord","addWord","search","search","search","search"]
    [[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]]
    输出:
    [null,null,null,null,false,true,true,true]
    
    解释:
    WordDictionary wordDictionary = new WordDictionary();
    wordDictionary.addWord("bad");
    wordDictionary.addWord("dad");
    wordDictionary.addWord("mad");
    wordDictionary.search("pad"); // return False
    wordDictionary.search("bad"); // return True
    wordDictionary.search(".ad"); // return True
    wordDictionary.search("b.."); // return True
    

     

    提示:

    • 1 <= word.length <= 500
    • addWord 中的 word 由小写英文字母组成
    • search 中的 word 由 '.' 或小写英文字母组成
    • 最多调用 50000addWordsearch
    Related Topics
  • 深度优先搜索
  • 设计
  • 字典树
  • 字符串

  • 👍 403
  • 👎 0
  • 【第一次写字典树】Java, 添加与搜索单词

    第一次写字典树,之前也遇到过相关题目,知道有字典树这么个概念,但是根据学习计划,一直都还没接触。正好本周开始接触字符串相关算法了,才学习了KMP、Sunday、Shift And,正好遇到这题直接第一反应就是字典树。
    在做题之前写去简单学习了下相关算法,按照本题题意,在没有`.`的时候写了第一版字典树算法

    class WordDictionary {
    
        TrieNode trieTree;
    
        public WordDictionary() {
            trieTree = new TrieNode('\u0000');
        }
    
        public void addWord(String word) {
            if (null == word || word.length() < 1){
                return;
            }
            int idx = -1;
            TrieNode cur = trieTree;
            while (++idx < word.length()){
                if (!cur.children.containsKey(word.charAt(idx))){
                    cur.children.put(word.charAt(idx),new TrieNode(word.charAt(idx)));
                }
                cur = cur.children.get(word.charAt(idx));
            }
            cur.isTail = true;
        }
    
        public boolean search(String word) {
            int idx = -1;
            TrieNode cur = trieTree;
            while (++idx < word.length()){
                if (cur.children.containsKey(word.charAt(idx))){
                    cur = cur.children.get(word.charAt(idx));
                }else{
                    return false;
                }
            }
            return cur.isTail;
        }
    
    
    
    
        class TrieNode {
            char value;
            boolean isTail;
            HashMap<Character,TrieNode> children;
    
            public TrieNode(char value) {
                this.value = value;
                children = new HashMap<>();
            }
        }
    }

    写完了上面的,再来看这题,比一般匹配中多了一个`.`的可能,如果遇到`.`则表示可以是任意字符,那么相应的,最直观的作法,我们应当匹配下当前`TrieNode`下的`children`中的每一个节点。

    在写代码之前

    1. `children`结构一开始依旧是使用的`Hashmap`,虽然最终结果能跑通,但是性能上还是有点差距,后来看了下,对应的节点值只会是小写字母,也就是说范围限定死了,那么我们可以把`children`结构改为`TrieNode[]`数组,对应的key从0-25开始映射,这是一个小技巧,在部分情况下用数组代替`Hashmap`能有效提升不少性能,
    2. 对于搜索部分,原本是`cur.children.get(word.charAt(idx))`,现在是不确定多少了,所以分情况判断,如果当前字符是`.`,则遍历children处理,如果不是则只处理对应的明确的那个节点。
    3. 终止判断。
        - 情况1:如果已经到了被搜索字符串结尾,而当前对应的节点不是一个结束节点,则返回false;
        - 情况2:如果已经到了被搜索字符串结尾,且当前对应节点是一个结束节点,返回true;
        - 情况3:在遍历过程中,寻找能和字符串匹配的下一个节点失败,返回false;
        - 情况4:分支已经走到结束了了,而字符串遍历还没结束

    那么,代码

    class WordDictionary {
    
        TrieNode trieTree;
    
        public WordDictionary() {
            trieTree = new TrieNode('\u0000');
        }
    
        public void addWord(String word) {
            if (null == word || word.length() < 1){
                return;
            }
            int idx = -1;
            TrieNode cur = trieTree;
            while (++idx < word.length()){
                int key = word.charAt(idx)-'a';
                if (cur.children[key] == null){
                    cur.children[key] = new TrieNode(word.charAt(idx));
                }
                cur = cur.children[key];
            }
            cur.isTail = true;
        }
    
        public boolean search(String word) {
            return innerSearch(word,0,trieTree);
        }
    
        private boolean innerSearch(String word, int wordIdx, TrieNode startNode){
            if (wordIdx == word.length() || null==startNode){
                return null==startNode? false:startNode.isTail;
            }
    
            if (word.charAt(wordIdx)=='.'){
                for (TrieNode child : startNode.children) {
                    if (innerSearch(word,wordIdx+1,child)){
                        return true;
                    }
                }
            }else if(innerSearch(word,wordIdx+1, startNode.children[word.charAt(wordIdx)-'a'])){
                    return true;
            }
    
            return false;
        }
    
    
        class TrieNode {
            char value;
            boolean isTail;
            TrieNode[] children;
    
            public TrieNode(char value) {
                this.value = value;
                children = new TrieNode[26];
            }
        }
    }

    跑了下结果,看起来勉强及格吧

    解答成功:
    执行耗时:40 ms,击败了67.98% 的Java用户
    内存消耗:49 MB,击败了55.66% 的Java用户

    今天第一次接触,有什么问题或者错误希望帮忙指出,感谢了

    LeetCode刷题【392】判断子序列

    给定字符串 st ,判断 s 是否为 t 的子序列。

    字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace""abcde"的一个子序列,而"aec"不是)。

    进阶:

    如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

    致谢:

    特别感谢 @pbrother 添加此问题并且创建所有测试用例。

     

    示例 1:

    输入:s = "abc", t = "ahbgdc"
    输出:true
    

    示例 2:

    输入:s = "axc", t = "ahbgdc"
    输出:false
    

     

    提示:

    • 0 <= s.length <= 100
    • 0 <= t.length <= 10^4
    • 两个字符串都只由小写字符组成。
    Related Topics
  • 双指针
  • 字符串
  • 动态规划

  • 👍 601
  • 👎 0
  • 简单双指针解法 & 更新添加动态规划解法 & 一维DP数组

    其实是想做dp来着。。。但是双指针看着太好理解了。写写个双指针

    class Solution {
        public boolean isSubsequence(String s, String t) {
            int idxS = -1;
            int idxT = -1;
            while (++idxS < s.length() && idxT < t.length()){
                while (++idxT < t.length()){
                    if (s.charAt(idxS) == t.charAt(idxT)){
                        break;
                    }
                }
            }
    
            return idxS == s.length() && idxT < t.length();
        }
    }
    1. 在字符s和t上各一个指针
    2. 从各自字符串起始位置开始,在t中找s上当前对应的字符,如果不是则t上的指针往后移动一位
    3. 如果相同则s上之后往后移动一位,并t上的指针继续往后寻找当前s上的指向的字符
    4. 最终如果s上的指针到结尾了,则表明是包含的

    动态规划

    在做了不少动态规划题目了,再看这个题目就非常简单了。
    直接拿官方示例来举例子,自己画下这个矩阵就一下子一目了然、豁然开朗了。

    如果还没看明白的话,我们把需要查找的子串换下再试试

    状态转移方程

    //如果当前字符相等
    dp[row][col] = dp[rol-1][col-1]
    //如果字符不等
    dp[row][col] = dp[rol][col-1]

    写份代码跑下试试吗,没毛病

    class Solution {
        public boolean isSubsequence(String s, String t) {
            boolean[][] dp = new boolean[s.length()+1][t.length()+1];
            Arrays.fill(dp[0],true);
            int row = 0;
            while (++row<=s.length()){
                int col = 0;{
                    while (++col<=t.length()){
                        dp[row][col] = s.charAt(row-1) == t.charAt(col-1)?dp[row-1][col-1]:dp[row][col-1];
                    }
                }
            }
            return dp[s.length()][t.length()];
        }
    }

    以及状态压缩一维DP数组,需要借一个`preStatus`记录前一位下标在更新前的值,也许还有其他更好的写法,暂时没想出来,可以留言多多交流

    class Solution {
        public boolean isSubsequence(String s, String t) {
            boolean[] dp = new boolean[t.length()+1];
            Arrays.fill(dp,true);
            int sIdx = -1;
            boolean preStatus = true;
            while (++sIdx<s.length()){
                int tIdx = 0;
                dp[0] = false;
                preStatus = sIdx==0;
                while (++tIdx <= t.length()){
                    boolean current = dp[tIdx];
                    dp[tIdx] = s.charAt(sIdx)==t.charAt(tIdx-1)?preStatus:dp[tIdx-1];
                    preStatus = current;
                }
            }
            return dp[t.length()];
        }
    }

    LeetCode刷题【5】最长回文子串

    给你一个字符串 s,找到 s 中最长的回文子串。

     

    示例 1:

    输入:s = "babad"
    输出:"bab"
    解释:"aba" 同样是符合题意的答案。
    

    示例 2:

    输入:s = "cbbd"
    输出:"bb"
    

     

    提示:

    • 1 <= s.length <= 1000
    • s 仅由数字和英文字母组成
    Related Topics
  • 字符串
  • 动态规划

  • 👍 4821
  • 👎 0
  • 动态规划

    看一看,想一想,这题踩得坑比较多。

    • 构建一个`N x N`的DP矩阵,
    • 首先在所有`dp[i][i]`格子上表示可以构成回文子串,毕竟单自己一个字符是可以算的(如图)
    • 构成回文有两种情况,一种是偶数长度的,中间两位是一样的,另一种是奇数长度的,中间单独一个前段和后段是一样的
    • 与之对应的两种初始情况:AA型长度为2的两个相同字符,或者ABA型长度为3的收尾相同的3个字符
    • 那么其他的情况当大于3的时候,假设字符串长度是k,且收尾字符串相同,则只需要判断中间的下标1到下标k-2是回文字符串就行

    如图,我们从较小的范围,最底部开始

    1. `dp[4][5]`位置对应结束字符横行的`b`和开始字符竖列的`b`两个字符相同,则判断此时距离为1,那么此时构成回文子串
    2. 继续往上直到`dp[2][4]`位置,首位相等都是`b`,且距离大于1,则判断中间部分是否为回文子串,中间部分只有一个`c`是回文子串,那么`dp[2][4]`是回文子串
    3. 继续往上到`dp[1][2]`,首位都是`b`且距离为1,是回文子串
    4. 到`dp[1][5]`首位都是b,且距离大于1,判断中间的`dp[2][4]`是否是回文子串,`dp[2][4]`是回文子串,呢么对应的`dp[1][5]`也是回文子串
    5. 后面就基本一样了,不用过多重复
    6. 在每次判断得到结果为是回文子串的时候,根据开始结束位置我们能得到一个长度,把这个长度记下来,并每次比较取较大的一个长度和对应的左边起始位置。
    7. 根据最终最大值的左起始位置和长度,构建返回新字符串。
    class Solution {
        public String longestPalindrome(String s) {
    
            if (s.length()<=1){
                return s;
            }
            int l = s.length()-1;
            int r = 0;
            int maxLength = 1;
            int maxL = 0;
            boolean[][]dp = new boolean[s.length()][s.length()];
            for (int i = 0; i < s.length(); i++) {
                dp[i][i] = true;
            }
            while (--l >= 0){
                r = l;
                while (++r < s.length()){
                    if (s.charAt(l)==s.charAt(r)){
                        if ( r==l+1){
                            dp[l][r] = dp[l + 1][r];
                        }else{
                            dp[l][r] = dp[l + 1][r - 1];
                        }
                    }
                    if (dp[l][r]){
                        if (maxLength < r-l+1){
                            maxL = l;
                            maxLength = r-l+1;
                        }
                    }
                }
            }
    
    
            char[] arr = new char[maxLength];
            System.arraycopy(s.toCharArray(),maxL,arr,0,maxLength);
            return new String(arr);
        }
    }

    LeetCode刷题【476】数字的补数

    对整数的二进制表示取反(0110)后,再转换为十进制表示,可以得到这个整数的补数。

    • 例如,整数 5 的二进制表示是 "101" ,取反后得到 "010" ,再转回十进制表示得到补数 2

    给你一个整数 num ,输出它的补数。

     

    示例 1:

    输入:num = 5
    输出:2
    解释:5 的二进制表示为 101(没有前导零位),其补数为 010。所以你需要输出 2 。
    

    示例 2:

    输入:num = 1
    输出:0
    解释:1 的二进制表示为 1(没有前导零位),其补数为 0。所以你需要输出 0 。
    

     

    提示:

    • 1 <= num < 231

     

    注意:本题与 1009 https://leetcode-cn.com/problems/complement-of-base-10-integer/ 相同

    Related Topics
  • 位运算

  • 👍 294
  • 👎 0
  • 简单做下,不用位运算 & 用位运算

    首先在不用位运算的情况下,我们理清下逻辑

    1. 要取反的话,用当前数字余2,结果为1则取0,结果为0则取1,得到的数字留在当前位置
    2. 然后接下来,对应二进制数往前找一位,我们用除法的除以2来实现,用`num = num /2`
    3. 再次对num值余2,结果为1则取0,结果为0则取1,得到的结果要放在之前(1)中的二进制数前面,那么简单的相应作法把当前数字乘以2再加上(1)的结果,得到一个新的结果
    4. 再次`num = num /2`
    5. 再次对num值余2,结果为1则取0,结果为0则取1,这次得到的结果需要放在(3)的结果前面,那么对应的这次的结果需要乘以2的平方
    6. 依此继续循环
    class Solution {
        public int findComplement(int num) {
            int res = 0;
            int i = 1;
            while ( num > 0){
                res +=  (num%2==0?1:0) * i;
                i *= 2;
                num /= 2;
            }
            return res;
        }
    }

    更进一步

    根据位运算相关规则,我们把上面的代码重新写一遍

    乘以i个2就可以直接改写成左移i次,同时把后面要执行的`i+1`的操作也一并带上的话就变成了`<< i++`

    余2判断奇偶性变成`num&1`,结果再取相反的那么就是`num&1^1`,注意不要使用`~`,这会把原来的1变成`11111111111111111111111111111110`,而0变成`11111111111111111111111111111111`

    而对应的`num = num /2`改为`num = num >> 1`

    class Solution {
    
        /**
         * 与&:0&0=0 0&1=0 1&0=0 1&1=1
         * 或|:0|0=0 0|1=1 1|0=1 1|1=1
         * 异或^:0^0=0 0^1=1 1^0=1 1^1=0
         * 取反~:~1=0 ~0=1
         * 左移<<:左边的二进制位丢弃,右边补0
         * 右移>>:正数左补0,负数左补1,右边丢弃
         * 无符号左移<<<:左边的二进制位丢弃,右边补0
         * 无符号右移>>>:忽略符号位,空位都以0补齐
         * @param num
         * @return
         */
        public int findComplement(int num) {
            int res = 0;
            int i = 0;
            while ( num > 0){
                res += (num&1^1) << i++;
                num = num >> 1;
            }
            return res;
        }
        
    }

    结束收工

    LeetCode刷题【230】二叉搜索树中第K小的元素

    给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。

     

    示例 1:

    输入:root = [3,1,4,null,2], k = 1
    输出:1
    

    示例 2:

    输入:root = [5,3,6,2,4,null,null,1], k = 3
    输出:3
    

     

     

    提示:

    • 树中的节点数为 n
    • 1 <= k <= n <= 104
    • 0 <= Node.val <= 104

     

    进阶:如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化算法?

    Related Topics
  • 深度优先搜索
  • 二叉搜索树
  • 二叉树

  • 👍 582
  • 👎 0
  • 【中序遍历 双解法】java中序遍历

    简单没啥好特别的,搜索树的特性,左小右大,依次遍历的话是从最小的读起的那么对应数组的第k-1位就是所求

    class Solution {
        List<Integer> list;
        public int kthSmallest(TreeNode root, int k) {
            list = new ArrayList<>();
            dfs(root);
            return list.get(k-1);
        }
    
        public void dfs(TreeNode node){
            if (node == null){
                return;
            }
            dfs(node.left);
            list.add(node.val);
            dfs(node.right);
        }
    }

    如上代码,既然我们知道了由小到大的特性,那么其实就不用遍历整个二叉树了,只要按个遍历了k个节点,将第k个节点返回即可

    class Solution {
        int num = 0;
        int k;
        public int kthSmallest(TreeNode root, int k) {
            this.k = k;
            return dfs(root);
    
        }
    
        public int dfs(TreeNode node){
            if (node == null){
                return -1;
            }
            int left = dfs(node.left);
            if (left >=0 ){
                return left;
            }
            if (++num == k){
                return node.val;
            }
            int right = dfs(node.right);
            if (right >=0 ){
                return right;
            }
            return -1;
        }
    }

    LeetCode刷题【剑指 Offer 05】替换空格

    请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

     

    示例 1:

    输入:s = "We are happy."
    输出:"We%20are%20happy."

     

    限制:

    0 <= s 的长度 <= 10000

    Related Topics
  • 字符串

  • 👍 229
  • 👎 0
  • 【一个变仨】有多少个空格就长度多多少个2呗

    不复杂,看代码遍历下看下有几个空格,一个空格变成`%20`的三个这样长度就多出来了2。然后把原来字符串s对应复制到新的char[]数组中,如果遇到了空格,就赋值为’%’和’2’和’0′,并且下标往后移动两位

    class Solution {
        public String replaceSpace(String s) {
            int count = 0;
            for (int i = 0; i < s.length(); i++) {
                count += s.charAt(i) == ' '?1:0;
            }
            if (count == 0){
                return s;
            }
            char[] arr = new char[s.length()+ count*2 ];
            int idxForS = -1;
            int idxForArr = 0;
            while (++idxForS < s.length()){
                if (s.charAt(idxForS) != ' '){
                    arr[idxForArr] = s.charAt(idxForS);
                }else{
                    arr[idxForArr] = '%';
                    arr[++idxForArr] = '2';
                    arr[++idxForArr] = '0';
                }
                idxForArr++;
            }
            return new String(arr);
        }
    }