给定一个只包括 ‘(‘,’)’,’{‘,’}’,’[‘,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
示例 1:
输入:s = “()” 输出:true
示例 2:
输入:s = “()[]{}” 输出:true
示例 3:
输入:s = “(]” 输出:false
示例 4:
输入:s = “([)]” 输出:false
示例 5:
输入:s = “{[]}” 输出:true
题解 遇到此类括号匹配题目,首先想到的就是栈了。
栈先入后出特点恰好与本题括号排序特点一致,即若遇到左括号入栈,遇到右括号时将对应栈顶左括号出栈,则遍历完所有括号后 stack 仍然为空;建立哈希表 Map构建左右括号对应关系:key左括号,value 右括号;这样查询 2 个括号是否对应只需 O(1) 时间复杂度;建立栈 stack,遍历字符串 s 并按照算法流程一一判断。
解法一:自己做的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var isValid = function (s ) { let s1 = s.split("" ); if (s1.length % 2 ){ return false ; } let stk = []; const m = new Map ([['(' ,')' ],['[' ,']' ],['{' ,'}' ]]); for (let i of s1){ if (m.get(i)){ stk.push(i); }else { if (i !== m.get(stk[stk.length-1 ])){ return false ; }else { stk.pop(); } } } return !stk.length; };
解法二:模仿别人做的。其实道理和解法一相同,只是建map的时候顺序有些不同,就会执行不同的操作,详情见代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var isValid = function (s ) { if (s.length % 2 ){ return false ; } let stk = []; const mm = new Map ([[')' ,'(' ],[']' ,'[' ],['}' ,'{' ]]); for (let i of s){ if (mm.get(i)){ if (stk[stk.length-1 ] !== mm.get(i)) return false ; else stk.pop(); }else { stk.push(i) } } return !stk.length; };
解法三:不利用Map的做法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var isValid = function (s ) { if (s.length % 2 ){ return false ; } let stk = []; for (let i of s){ switch (i){ case "(" : case "[" : case "{" : stk.push(i); break ; case ")" : if (stk.pop()!== "(" ) return false ; break ; case "]" : if (stk.pop()!== "[" ) return false ; break ; case "}" : if (stk.pop()!== "{" ) return false ; break ; } } return !stk.length; };
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例 1:
输入:s = “3[a]2[bc]” 输出:”aaabcbc”
示例 2:
输入:s = “3[a2[c]]” 输出:”accaccacc”
示例 3:
输入:s = “2[abc]3[cd]ef” 输出:”abcabccdcdcdef”
示例 4:
输入:s = “abc3[cd]xyz” 输出:”abccdcdcdxyz”
题解 用栈来做吧(多看多复习,这个有点绕,画画图最好!)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var decodeString = function (s ) { let numStack = []; let strStack = []; let num = 0 ; let result = '' ; for (const char of s) { if (!isNaN (char)) { num = num * 10 + Number (char); } else if (char == '[' ) { strStack.push(result); result = '' ; numStack.push(num); num = 0 ; } else if (char == ']' ) { let repeatTimes = numStack.pop(); result = strStack.pop() + result.repeat(repeatTimes); } else { result += char; } } return result; };
Trie (发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
请你实现 Trie 类:
Trie()
初始化前缀树对象。
void insert(String word)
向前缀树中插入字符串 word
。
boolean search(String word)
如果字符串 word
在前缀树中,返回 true
(即,在检索之前已经插入);否则,返回 false
。
boolean startsWith(String prefix)
如果之前已经插入的字符串 word
的前缀之一为 prefix
,返回 true
;否则,返回 false
。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 输入 ["Trie", "insert", "search", "search", "startsWith", "insert", "search"] [[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]] 输出 [null, null, true, false, true, null, true] 解释 Trie trie = new Trie(); trie.insert("apple"); trie.search("apple"); // 返回 True trie.search("app"); // 返回 False trie.startsWith("app"); // 返回 True trie.insert("app"); trie.search("app"); // 返回 True
题解 Trie,又称前缀树或字典树,是一棵有根树,其每个节点包含以下字段:
指向子节点的指针数组 children。对于本题而言,数组长度为 26,即小写英文字母的数量。此时 children[0]对应小写字母 a,children[1] 对应小写字母 b,…,children[25] 对应小写字母 z。
布尔字段 isEnd,表示该节点是否为字符串的结尾。
插入字符串
我们从字典树的根开始,插入字符串。对于当前字符对应的子节点,有两种情况:
子节点存在。沿着指针移动到子节点,继续处理下一个字符。
子节点不存在。创建一个新的子节点,记录在 children数组的对应位置上,然后沿着指针移动到子节点,继续搜索下一个字符。
重复以上步骤,直到处理字符串的最后一个字符,然后将当前节点标记为字符串的结尾。
查找前缀
我们从字典树的根开始,查找前缀。对于当前字符对应的子节点,有两种情况:
子节点存在。沿着指针移动到子节点,继续搜索下一个字符。
子节点不存在。说明字典树中不包含该前缀,返回空指针。
重复以上步骤,直到返回空指针或搜索完前缀的最后一个字符。
若搜索到了前缀的末尾,就说明字典树中存在该前缀。此外,若前缀末尾对应节点的 isEnd 为真,则说明字典树中存在该字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 var Trie = function ( ) { this .children = {}; }; Trie.prototype.insert = function (word ) { let node = this .children; for (const ch of word) { if (!node[ch]) { node[ch] = {}; } node = node[ch]; } node.isEnd = true ; }; Trie.prototype.search = function (word ) { const node = this .searchPrefix(word); return node !== undefined && node.isEnd !== undefined ; }; Trie.prototype.startsWith = function (prefix ) { return this .searchPrefix(prefix); }; Trie.prototype.searchPrefix = function (prefix ) { let node = this .children; for (const ch of prefix) { if (!node[ch]) { return false ; } node = node[ch]; } return node; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 var Trie = function ( ) { this .children={} }; Trie.prototype.insert = function (word ) { let node = this .children for (let ch of word){ if (!node[ch]){ node[ch]={} } node = node[ch] } node.isEnd = true }; Trie.prototype.search = function (word ) { let node = this .children for (let ch of word){ if (!node[ch]){ return false } node = node[ch] } return node!==undefined &&node.isEnd!==undefined }; Trie.prototype.startsWith = function (prefix ) { let node = this .children for (let ch of prefix){ if (!node[ch]){ return false } node = node[ch] } return true };
请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。
实现词典类 WordDictionary :
WordDictionary() 初始化词典对象
void addWord(word) 将 word 添加到数据结构中,之后可以对它进行匹配
bool search(word) 如果数据结构中存在字符串与 word 匹配,则返回 true ;否则,返回 false 。word 中可能包含一些 '.' ,每个 . 都可以表示任何一个字母。
题解 还是可以用字典树。如上题的思路。
对于记录/查找单词的情景,有种数据结构非常高效:Trie。我们可以构造一棵字典树,每次调用 addWord 时候,将单词存入字典树。注意:当调用 search 进行查找的时候,如果当前字符不是.,那么就按照字典树的查找逻辑;否则,由于是通配符,要遍历当前的节点的 next 中的所有字符,这个过程和 DFS 一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 var WordDictionary = function ( ) { this .root = {}; }; WordDictionary.prototype.addWord = function (word ) { let node = this .root; for (let c of word) { if (!node[c]) { node[c] = {}; } node = node[c]; } if (!node.END) { node.END = true ; } }; WordDictionary.prototype.search = function (word ) { function _search (word, root ) { let node = root; for (let i = 0 ; i < word.length; i++) { let c = word[i]; if (!node[c]) { if (c === "." ) { for (let p in node) { if (!p || p === "END" ) continue ; if (_search(word.slice(i + 1 ), node[p])) { return true ; } } } return false ; } node = node[c]; } return node && !!node.END; } return _search(word, this .root); };
给定两个字符串 s 和 t,它们只包含小写字母。
字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。
请找出在 t 中被添加的字母。
示例 1:
输入:s = “abcd”, t = “abcde” 输出:”e” 解释:’e’ 是那个被添加的字母。
示例 2:
输入:s = “”, t = “y” 输出:”y”
示例 3:
输入:s = “a”, t = “aa” 输出:”a”
示例 4:
输入:s = “ae”, t = “aea” 输出:”a”
题解 1.求和方法
将字符串 s 中每个字符的 ASCII 码的值求和,得到 sum1;对字符串 t 同样的方法得到 sum2。两者的差值 sum2-sum1 即代表了被添加的字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var findTheDifference = function (s, t ) { let sum1 = 0 ,sum2 =0 ; for (let i =0 ;i<s.length;i++){ sum1 += s.charCodeAt(i); } for (let j =0 ;j<t.length;j++){ sum2 += t.charCodeAt(j); } return String .fromCharCode(sum2-sum1); }
2.计数方法
首先遍历字符串 s,对其中的每个字符都将计数值加 1;然后遍历字符串 t,对其中的每个字符都将计数值减 1。当发现某个字符计数值为负数时,说明该字符在字符串 t中出现的次数大于在字符串 s中出现的次数,因此该字符为被添加的字符。
1 2 3 4 5 6 7 8 9 10 11 12 var findTheDifference = function (s, t ) { const m = new Array (26 ).fill(0 ); for (s of s){ m[s.charCodeAt()-a.charCodeAt()]++; } for (t of t){ m[t.charCodeAt()-a.charCodeAt()]--; if (m[t.charCodeAt()-a.charCodeAt()]<0 ){ return t; } } }
3.位运算(运用异或)
如果将两个字符串拼接成一个字符串,则问题转换成求字符串中出现奇数次的字符。类似于「136. 只出现一次的数字 」,我们使用位运算的技巧解决本题。(位运算不是很熟,需要多练)
1 2 3 4 5 6 7 8 9 10 var findTheDifference = function (s, t ) { let ret = 0 ; for (const ch of s) { ret ^= ch.charCodeAt(); } for (const ch of t) { ret ^= ch.charCodeAt(); } return String .fromCharCode(ret); };
4.HashMap 方法(自己最开始想到的一种方法,但是我忽略了哈希表里面的key是不能重复的,如有重复,后面的会覆盖前面的key)方法是遍历第一个字符串标记出现的字符数量,再遍历第二个减去出现的数量,直到遇到为 0 或者原哈希表就不存在的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var findTheDifference = function (s, t ) { var map = new Map (); for (let i of s){ const val = map.get(i); map.set(i,val === undefined ? 1 :val+1 ); } for (let j of t){ const val = map.get(j); if (map.has(j)&&val>0 ){ map.set(j,val-1 ); } if (!map.has(j)||val==0 ){ return j; } } }
给定一种规律 pattern 和一个字符串 str ,判断 str 是否遵循相同的规律。
这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应规律。
示例1:
输入: pattern = “abba”, str = “dog cat cat dog” 输出: true
示例 2:
输入:pattern = “abba”, str = “dog cat cat fish” 输出: false
示例 3:
输入: pattern = “aaaa”, str = “dog cat cat dog” 输出: false
示例 4:
输入: pattern = “abba”, str = “dog dog dog dog” 输出: false
题解 (1)indexOf()派上用场了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var wordPattern = function (pattern, str ) { var arr = str.split(' ' ); if (pattern.length!=arr.length) return false ; for (var i=0 ;i<pattern.length;i++){ if (pattern.indexOf(pattern[i])!=arr.indexOf(arr[i])){ return false ; } } return true ; };
(2)map用法(注意:这里为什么要用两个map,因为两者是一一对应的,一个字符串的字符对应着唯一的单词,一个唯一的单词又对应着唯一的字符串,即双射,需要两个map来保证一一对应)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var wordPattern = function (pattern, str ) { var arr = str.split(' ' ); var map = new Map (); var maps = new Map (); if (pattern.length!=arr.length) return false ; for (var i=0 ;i<pattern.length;i++){ if (maps.has(arr[i])==false ){ maps.set(arr[i],pattern[i]); }else { if (maps.get(arr[i])!=pattern[i]){ return false } } if (map.has(pattern[i])==false ){ map.set(pattern[i],arr[i]); }else { if (map.get(pattern[i])!=arr[i]){ return false } } } return true ; };
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
示例:
1 2 3 4 5 s = "leetcode" 返回 0 s = "loveleetcode" 返回 2
题解 1.使用Map遍历两次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var firstUniqChar = function (s ) { let map = new Map (); for (let i=0 ;i<s.length;i++){ map.set(s[i],(map.get(s[i])||0 )+1 ); } for (let j=0 ;j<s.length; j++){ if (map.get(s[j])==1 ){ return j; } } return -1 ; };
利用indexOf和lastIndexOf来判断
1 2 3 4 5 6 7 8 9 var firstUniqChar = function (s ) { for (let i=0 ;i<s.length;i++){ if (s.indexOf(s[i]) === s.lastIndexOf(s[i])){ return i; } } return -1 ; };