leetcode-字符串算法1
// 算法 // 排列组合 - 称砝码 let weightArr = [1, 2] let numsArr = [2, 1] let allWeightArr = [] numsArr.forEach((n, i) => { allWeightArr = allWeightArr.concat(new Array(n).fill(weightArr[i])) }) console.log(allWeightArr) let weightSumsArr = [] // 几种组合 let result = [0] //几种重量 // 遍历每一个, 跟组合好的数组重新组合形成新的数组,放入新数组 allWeightArr.forEach((w) => { let newArr = [] weightSumsArr.forEach((item) => { let arr = [] arr = [...item, w] weightSumsArr.push(arr) }) weightSumsArr.push([w]) }) allWeightArr.forEach((w) => { let newArr = [] result.forEach((item) => { newArr.push(w + item) }) result = [...new Set([...result, ...newArr])] }) console.log(result, result.length) /* weightSumsArr = allWeightArr.reduce((pre, w) => { pre.forEach(item => { let arr2 = [...item, w] pre.push(arr2) }) pre.push([w]) return pre }, []) console.log(weightSumsArr) */ console.log(weightSumsArr) //----------------------------面试题 08.08. 有重复字符串的排列组合----------------------- //----------------------------面试题 08.07. 无重复字符串的排列组合----------------------- // 全排列 str = 'abc' 思路: 取出第一个,剩下的全排列,然后插入全排列的每一项。递归 + 回溯 function func (str) { let result = [] if (str.length === 1 || str.length === 0) { result.push(str) return [...new Set(result)] } let one = str[0] let other = str.slice(1) // 取出第一个, 剩下的全排列 ['bc', 'cb'] let otherCopmose = func(other) // 然后插入a, 有 3种方式 'abc' 'bac' 'cba' for(let i=0; i<otherCopmose.length; i++) { // 每次插入,都有 每组长度 + 1 中可能 for(let j=0; j< otherCopmose[i].length + 1; j++) { let arr = otherCopmose[i].split('') arr.splice(j, 0, one) let newStr = arr.join('') result.push(newStr) } } return [...new Set(result)] } func('abc') var func = function (str) { let result = [] function search(path) { if (path.length === str.length) { result.push(path) } for (let char of str) { if (path.indexOf(char) == -1) { search(`${char}${path}`) } } } search('') return result } func('abc') //-----------------------------------剑指 Offer II 017. 含有所有字符的最短字符串 难度:困难--------------------- //输入:s = "ADOBECODEBANC", t = "ABC" //输出:"BANC" //"acbbaca" //"aba" var minWindow = function(s, t) { let result = [] if (s.length < t.length) return '' let tmap = {} for (let ts of t) { tmap[ts] = tmap[ts] ? tmap[ts] + 1 : 1 } for (let i=0;i<s.length;i++) { for (let j=i+1; j<=s.length; j++) { let ss = s.slice(i, j) let ssmap = {} for (let sss of ss) { ssmap[sss] = ssmap[sss] ? ssmap[sss] + 1 : 1 } let flag = true for (let tss of t) { if (tmap[tss] > ssmap[tss] || !ss.includes(tss) ) { flag = false break } } if (flag && ss.length >= t.length) { result.push(ss) } } } // console.log(result) let sortSubs = result.sort((a, b) => a.length - b.length) if (sortSubs.length == 0) return '' return sortSubs[0] // console.log(sortSubs) }; //-------------------字符串所有子串 - 递归--------------------------- var strFromSubStr = function (str) { let result = [] function dfs (s) { if (s.length === 1) { result.push(s) return result } let one = s[0] let subs = dfs(s.slice(1)) subs.forEach(ss => { result.push(one, one + ss) }) return result } dfs(str) return [...new Set(result)] } //---------------------------剑指 Offer II 015. 字符串中的所有变位词------------------------------- //输入: s = "cbaebabacd", p = "abc" //输出: [0,6] var findAnagrams = function(s, p) { let res = [] let pmap = {} for (let c of p) { pmap[c] = pmap[c] ? pmap[c] + 1 : 1 } for(let i=0; i<s.length; i++) { let target = s.slice(i, i+p.length) // target 和 p 是变位词 if (target.length !== p.length) continue let tmap = {} for (let c of target) { tmap[c] = tmap[c] ? tmap[c] + 1 : 1 } let flag = true let keys = Object.keys(tmap) for (let j=0; j<keys.length; j++) { if (tmap[keys[j]] !== pmap[keys[j]]) { flag = false break } } if (!flag) continue res.push(i) } return res }; //-----------------------剑指 Offer II 014. 字符串中的变位词: s1 是否是 s2 的变位词--------------- var checkInclusion = function(s1, s2) { for(let i=0; i<s2.length; i++) { let ts = s2.slice(i, i+s1.length) if (ts.length !== s1.length) continue if (ts.split('').sort().join('') === s1.split('').sort().join('')) { return true } else { continue } } return false }; // 双指针解法 var checkInclusion = function(s1, s2) { let need = new Array(26).fill(0) let len1 = s1.length let len2 = s2.length for (let s of s1) { let i = s.charCodeAt() - 97 need[i] += 1 } // console.log(need) let l=0, r=0; let all = new Array(26).fill(0) while (r<len2) { all[s2[r++].charCodeAt() - 97] += 1 if (r-l === len1) { if (need.toString() === all.toString()) { console.log(all) return true } all[s2[l++].charCodeAt() - 97] -= 1 } } return false } //-------------------剑指 Offer II 086. 分割回文子字符串---------------------- // 输入: 'google' // 输出: [ ["g", "o", "o", "g", "l", "e"],["g", "oo", "g", "l", "e"], ["goog", "l", "e"] ] var partition = function(s) { let res = [] let arr = [] function search (index) { if (index === s.length) { return res.push(arr) } let target = '' for (let i=index; i<s.length; i++) { target += s[i] if (target !== target.split('').reverse().join('')) continue arr.push(target) search(i + 1) arr.pop() } } search(0) return res }; // ------------------最长无重复子串---------------------------- var lengthOfLongestSubstring = function(s) { let max = 0 let left = 0 let right = 1 let l = 0 let r = 0 if (s.length === 0 || s.length === 1) return s.length while (right < s.length) { let sub = s.slice(left, right) if (sub.indexOf(s[right]) > -1) { left++ continue } else { right++ } if (right - left > max) { max = right - left l = left r = right } } console.log(s.slice(l, r)) return s.slice(l, r).length }; //---------------------------------------516. 最长回文子序列------------- var longestPalindromeSubseq = function(s) { let len = s.length let dp = Array.from(new Array(len),()=>new Array(len).fill(0)) for(let i = len-1;i>=0;i--){ dp[i][i] = 1 for(let j = i+1;j<len;j++){ if(s[i]==s[j]){ // i到j 满足回文的话,去掉第一个和最后一个字符依然是回文。 i+1 去掉首,j-1 去掉尾。 去掉了两个字符,所以长度是 +2 dp[i][j] = dp[i+1][j-1] +2 }else{ // i到j 不满足的话,取 i+1 ~ j 和 i~j-1 最长的 dp[i][j] = Math.max(dp[i+1][j],dp[i][j-1]) } } } return dp[0][len-1] }; // -----------------------leetcode 8.字符串转整数 -------------------------- var myAtoi = function(str) { //利用正则 let result = str.trim().match(/^[-|+]{0,1}[0-9]+/) if(result != null){ if(result[0] > (Math.pow(2,31) -1)){ return Math.pow(2,31) -1 } if(result[0] < Math.pow(-2,31)){ return Math.pow(-2,31) } return result[0] } return 0 }; //------------------------leetcode 205. 同构字符串: 哈希表---------------------------- // 输入:s = "egg", t = "add" 正向:e->a g->d 反向:a->e d->g // 输出:true var isIsomorphic = function(s, t) { function helper (str1, str2) { let map = {} let len = str1.length for (let i=0; i<len; i++) { let s1 = str1[i] let t1 = str2[i] if (map[s1]) { if (map[s1] !== t1) { return false } } else { map[s1] = t1 } } console.log(map) return true } return helper(s, t) && helper(t, s) }; //-------------------------leetcode 43 字符串数字相乘, 大数相乘--------------- var multiply = function(num1, num2) { if (num1 === '0' || num2 === '0') { return '0' } let arr1 = num1.split('').reverse() let arr2 = num2.split('').reverse() let L1 = arr1.length let L2 = arr2.length let result = new Array(L1+L2).fill(0) for (let i=0; i<L1; i++) { for(let j=0; j<L2; j++) { let carryIndex = i + j + 1 //进位位置 let curIndex = i + j //当前位置 let sum = arr1[i] * arr2[j] + result[curIndex] // 当前的和 = 当前的乘积 + 当前位置的值(上次进位的值) result[curIndex] = sum % 10 //当前位置的值 result[carryIndex] += Math.floor(sum / 10) // 进位位置的值 = 进位位置原来的值 + sum / 10 取整 } } console.log(result) let res = result.reverse() if (res[0] === 0) res.shift() return res.join('') }; // ---------------------------------------------------动态规划类问题:1. 计数, 2. 最值, 3. 是否存在------------------------------------------------------- /** 1.确定状态- 最后一步 + 子问题 2.总结方程 3.边界条件 4.计算顺序 */ 案例1:有三种硬币,分别面值2元,5元和7元,每种硬币都有足够多,买一本书需要27元,如何用最少的硬币组合正好付清,不需要对方找钱 // 分析:1. 最后一步 27-ak let coins = [2, 5, 7] function coinsChange (coins, sum) { let f = new Array(sum + 1).fill(0) f[0] = 0; // 从 f(1) 开始计算一直到 f(27) for (let i=1; i<=sum; i++) { f[i] = Number.POSITIVE_INFINITY //f[X]=min{f[X-2]+1,f[X-5]+1,f[X-7]+1} for (let j = 0; j<coins.length; j++) { if (i >= coins[j] && f[i - coins[j]] !== Number.POSITIVE_INFINITY) { // 取上一次的最小 + 当前 f[i] = Math.min(f[i], f[i-coins[j]] + 1) } } } if(f[sum] == Number.POSITIVE_INFINITY) { return -1 } return f[sum] } coinsChange([2,5,7], 27) // dp数组优化双重循环带来的重复计算 var coinsChange = (coins, N) => { let dp = new Array(N+1).fill(Infinity); dp[0] = 0 if (N < 0) return -1 for(let i=0; i<dp.length; i++) { for(let j=0; j<coins.length; j++) { if (i-coins[j] < 0) continue dp[i] = Math.min(dp[i], 1 + dp[i-coins[j]]) } } return dp[N] } coinsChange([2,5,7], 27) // 案例2: 给定m行n列的网格,有一个机器人从左上角(0,0)出发,每一步可以向下或者向右走一步, 有多少种不同的方式走到右下角 function skipGEZI (m, n) { let f = new Array(m).fill(new Array(n).fill(0)) for (let i=0; i<m; i++) { for (let j=0; j<n; j++) { if(j === 0 || i === 0) { f[i][j] = 1 } else { f[i][j] = f[i-1][j] + f[i][j-1] } } } return f[m-1][n-1] } // 案例3:给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标 // 1. 最后一步 a[i] + i >= j j代表某个位置 const jumpCan = (arr) => { let len = arr.length let f = new Array(len).fill(false) f[0] = true for (let i=0; i< len; i++) { for (let j=0; j<i; j++) { if (f[j] && arr[j] + j >= i) { f[i] = true } } } return f[len -1] } jumpCan([2,3,1,1,4]) // true jumpCan([3,2,1,0,4]) // false //------------------------------------------leetcode 97.交错字符串------------------------ // 输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac" // 输出:true 下面解法不对,应该用动态规划。 var isInterleave = function(s1, s2, s3) { let arr1 = s1.split('') let arr2 = s2.split('') let all = [...arr1, ...arr2] if (all.length !== s3.length) return false let allmap = all.reduce((map, cur) => { if (map[cur]) { map[cur] += 1 } else { map[cur] = 1 } return map }, {}) let flag = true let map = {} for (let ch of s3) { if (!all.includes(ch)) { flag = false break } if (map[ch]) { map[ch] += 1 } else { map[ch] = 1 } } let keys = Object.keys(allmap) for (let j=0; j<keys.length; j++) { if (allmap[j] !== map[j]) { flag = false break } } function swrapArr (ar1, ar2) { // return ar1.filter((v) => !ar2.includes(v)) let _ar1 = [...ar1] for (let c of ar2) { let index = _ar1.findIndex(v => v===c) if (index > -1) { _ar1.splice(index, 1) } } return _ar1 } let s3arr = s3.split('') let _arr1 = swrapArr(s3arr, arr2) // 剩余的arr1 let _arr2 = swrapArr(s3arr, arr1) // 剩余的arr2 console.log(s3arr) console.log(_arr1, arr1) console.log(_arr2, arr2) if (arr1.sort().join('') !== _arr1.sort().join('')) { flag = false } if (arr2.sort().join('') !== _arr2.sort().join('')) { flag = false } return flag }; // 正确解法 - 剑指 Offer II 096. 字符串交织 var isInterleave = function(s1, s2, s3) { const n = s1.length; const m = s2.length; if(n+m!=s3.length) return false; // dp 路径 let dp = new Array(n+1).fill(0).map(()=>new Array(m+1).fill(false)); // dp[i][j] : 长度为[i+j]的s3前缀 能否由 长度为i的s1前缀 与 长度为j的s2前缀 交织组成 // 先处理一下 i/j 取0 的情况 dp[0][0] = true; for(let i=1;i<n+1;i++) { if(s1[i-1]==s3[i-1]) dp[i][0] = true; else break; } for(let j=1;j<m+1;j++) { if(s2[j-1]==s3[j-1]) dp[0][j] = true; else break; } for(let i=1;i<n+1;i++) { for(let j=1;j<m+1;j++) { dp[i][j] = (s1[i-1] == s3[i+j-1] && dp[i-1][j]) || (s2[j-1] == s3[i+j-1] && dp[i][j-1]) } } return dp[n][m]; }; //-------------------------97. 交错字符串/ 剑指 Offer II 096. 字符串交织: s3 是否是由 s1 和 s2 交错 组成 ------------------------------- //dp[i][j] 表示 s1.substring(0, i) 和 s2.substring(0, j) 能交错组成 s3.substring(0, i+j) //dp[0][0] = true //dp[i][j] = (dp[i-1][j] && s1[i-1] === s3[i+j-1]) || (dp[i][j-1] && s2[j-1] === s3[i+j-1]); var isInterleave = function(s1, s2, s3) { const m = s1.length + 1, n = s2.length + 1; if (s3.length !== m + n - 2) return false; const dp = []; for (let i = 0; i < m; ++i) { const temp = new Array(n); dp.push(temp); } dp[0][0] = true; for (let i = 1; i < m; ++i) { dp[i][0] = dp[i-1][0] && s1[i-1] === s3[i-1]; } for (let j = 1; j < n; ++j) { dp[0][j] = dp[0][j-1] && s2[j-1] === s3[j-1]; } for (let i = 1; i < m; ++i) { for (let j = 1; j < n; ++j) { dp[i][j] = (dp[i-1][j] && s1[i-1] === s3[i+j-1]) || (dp[i][j-1] && s2[j-1] === s3[i+j-1]); } } return dp[m-1][n-1]; }; // 案例4:斐波那切数列 dp数组迭代法 function fib(n) { let dp = new Array(n+1) dp[0] = dp[1] = 1 for (let i=3; i<=n; i++) { dp[i] = dp[i-1] + dp[i-2] } return dp[n] } //---------------爬楼梯 每次1级2级 有多少种方法------------------- var climbStairs = function(n) { let dp = new Array(n+1).fill(0) if (n === 1 || n === 2) { return n } dp[0] = 0 dp[1] = 1 dp[2] = 2 for (let i=3; i<=n; i++) { dp[i] = dp[i-1] + dp[i-2] } console.log(dp) return dp[n] }; //----------------------------面试题 05.02. 二进制小数数转字符串---------------------- var printBin = function(num) { let dist = [] while (num) { num *=2 let d = num >= 1 ? 1 : 0 dist.push(d) if (dist.length > 32) return 'ERROR' num -= d } return `0.${dist.join('')}` };