程序员面试金典01部分(JavaScript)
字符串注意点
字符串本质是字符类型的一维数组,但专属于数组的方法并不适用
判定字符是否唯一
实现一个算法,确定一个字符串 s 的所有字符是否全都不同。
示例 1:
输入: s = “leetcode”
输出: false
示例 2:
输入: s = “abc”
输出: true
限制:
0 <= len(s) <= 100
如果你不使用额外的数据结构,会很加分。
解题思路1
暴力解题
var isUnique = function(astr){
for(var i=0;i<astr.length;i++){
for(var j=i+1;j<astr.length;j++){
if(astr[i] == astr[j]){
return false
}
}
}
return true
}
解题思路2
直接通过比较元素第一次出现的下标和最后一次出现的下标是否相等,如果有不相等的就说明有重复的字符
var isUnique = function(astr){
for(var i=0;i<astr.length;i++){
if(astr.indexOf(astr[i]) !== astr.lastIndexOf(astr[i])){
return false
}
}
return true
}
解题思路3
利用ES6新增的数据结构方法Set,可以去除重复字符
var isUnique = function(astr){
return new Set(astr).size == astr.length
}
关于Set方法
1.set对象的声明
let set = new Set()
2.获取元素数量
set.size
3.检测元素是否存在
set.has(' ')
4.删除元素
set.delete(' ')
4.清除元素
set.clear()
5.将set对象转化为数组
//方法一
const set = new Set([1,2,3,4,4,]);
[...set];
//方法二
Array.from(set);
// 可以直接用 for of遍历Set
// for in 和 for of的区别是:in 是遍历对象,of是遍历值
for (let x of set) {
console.log(x);
}
// 实现并集、交集、差集
let a = new Set([1,2,3]);
let b = new Set([4,3,2]);
let union = new Set([...a, ...b]);//并集
let intersect = new Set([...a].filter(x => b.has(x)));//交集
let difference = new Set([...a].filter(x => !b.has(x)));//差集
判定是否互为字符重排
给定两个字符串 s1 和 s2,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。
示例 1:
输入: s1 = “abc”, s2 = “bca”
输出: true
示例 2:
输入: s1 = “abc”, s2 = “bad”
输出: false
说明:
0 <= len(s1) <= 100
0 <= len(s2) <= 100
解题思路1
暴力解法 判断s1字符是否存在于s2(注意s1=aab和s2=abb这种情况,需要对s2进行处理)
var CheckPermutation = function(s1, s2) {
if(s1.length == s2.length && s1 !== s2){
s2 = [...s2];
for(var i=0;i<s1.length;i++){
if(s2.indexOf(s1[i]) == -1){
return false
}else{
var a = s2.indexOf(s1[i]);
s2.splice(a,1)
}
}
return true
}else{
return false
}
};
解题思路2
利用数组排序的思想 如果排完序两个数组相等 则可以
var CheckPermutation = function(s1, s2) {
return [...s1].sort().join('') == [...s2].sort().join('')
};
关于判断两个数组是否相等
1、可以先把数组排序按照从小到大的顺序sort()函数
[1,2,3].sort().toString()== [3,2,1].sort().toString()
2、如果数组里的元素是标量,非object类型,可以使用==比较数组里的元素:
val scalarArrayEquals(array1,array2) {
return array1.length==array2.length && array1.every(function(v,i)
{
return v === array2[i]
});
}
URL化
URL化。编写一种方法,将字符串中的空格全部替换为%20。假定该字符串尾部有足够的空间存放新增字符,并且知道字符串的“真实”长度。
示例 1:
输入:"Mr John Smith ", 13
输出:"Mr%20John%20Smith"
示例 2:
输入:" ", 5
输出:"%20%20%20%20%20"
(参数1是给定字符串,参数二是字符串内容长度)
解题思路1
利用split和join
var replaceSpaces = function(S, length) {
return S.substr(0,length).split(' ').join('%20')
};
解题思路2
利用正则(要进行全局匹配)
var replaceSpaces = function(S, length) {
return S.substr(0,length).replace(/\s/g,'%20')
};
解题思路3
利用js自带API
var replaceSpaces = function(S, length) {
return encodeURI(S.substring(0,length))
};
对于URL的编码问题有三个API,分别是escape()、encodeURI()、encodeURIComponent()
- escape()除了 ASCII 字母、数字和特定的符号外,对传进来的字符串全部进行转义编码,因此如果想对URL编码,最好不要使用此方法。
- encodeURI() 用于编码整个URI,因为URI中的合法字符都不会被编码转换。
- encodeURIComponent方法在编码单个URIComponent(指请求参 数)应当是最常用的,它可以将参数中的中文、特殊字符进行转义,而不会影响整个URL。
以下展示测试用例
console.log(escape("http://www.w3school.com.cn"));// http%3A//www.w3school.com.cn
console.log((escape("http://www.w3school.com.cn/My first/")));//http%3A//www.w3school.com.cn/My%20first/
console.log(escape("?!=()#%&")); // %3F%21%3D%28%29%23%25%26
console.log('\n');
console.log((encodeURI("http://www.w3school.com.cn"))); //http://www.w3school.com.cn
console.log((encodeURI("http://www.w3school.com.cn/My first/"))); // http://www.w3school.com.cn/My%20first/
console.log((encodeURI(",/?:@&=+$#"))); // ,/?:@&=+$#
console.log('\n');
console.log((encodeURIComponent("http://www.w3school.com.cn"))); // http%3A%2F%2Fwww.w3school.com.cn
console.log((encodeURIComponent("http://www.w3school.com.cn/p 1/id?=giao"))); // http%3A%2F%2Fwww.w3school.com.cn%2Fp%201%2Fid%3F%3Dgiao
console.log((encodeURIComponent(",/?:@&=+$#")))//%2C%2F%3F%3A%40%26%3D%2B%24%23
回文排列
给定一个字符串,编写一个函数判定其是否为某个回文串的排列之一。
回文串是指正反两个方向都一样的单词或短语。排列是指字母的重新排列。回文串不一定是字典当中的单词。
示例1:
输入:“tactcoa”
输出:true(排列有"tacocat"、“atcocta”,等等)
解题思路
若给定字符串中字符个数为奇数的总数超过1个,则不是回文数列
var canPermutePalindrome = function(s) {
var stringObj = {};
var number = 0;
for(var i=0;i<s.length;i++){
if(stringObj[s[i]]){
stringObj[s[i]]++;
}else{
stringObj[s[i]] = 1;
}
};
for(var k in stringObj){
if(stringObj[k]%2 == 1){
number++;
}
};
if(number>1){
return false
}else{
return true
}
};
一次编辑
字符串有三种编辑操作:插入一个字符、删除一个字符或者替换一个字符。 给定两个字符串,编写一个函数判定它们是否只需要一次(或者零次)编辑。
示例 1:
输入:
first = “pale”
second = “ple”
输出: True
示例 2:
输入:
first = “pales”
second = “pal”
输出: False
解题思路
首先如果要满足条件,需要两个字符串长度相差一以内,相差一为插入或删除,相等为替换,其余为false,字符长度相等时,若两个字符串有两个以上字符不相同,则false,字符长度相差一时,当出现字符不同时,截取长字符串字符不同出现处的后半段(不包括字符不同处),截取短字符串字符不同出现处的后半段(包括字符不同处),若相等,则为true
var oneEditAway = function(first, second) {
var number = 0;
var n = first.length>second.length?second.length:first.length;
var smallString = (n==first.length?first:second);
var longString = (n==first.length?second:first);
if(Math.abs(first.length - second.length)==1){
for(var i=0;i<n;i++){
if(first[i]!=second[i]){
if(longString.substr(i+1)!=smallString.substr(i)){
return false
}
}
}
return true
}else if(first.length == second.length){
for(var i=0;i<first.length;i++){
if(first[i] !== second[i]){
number++
}
}
return number<=1;
}else{
return false
}
};
字符串压缩
字符串压缩。利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能。比如,字符串aabcccccaaa会变为a2b1c5a3。若“压缩”后的字符串没有变短,则返回原先的字符串。你可以假设字符串中只包含大小写英文字母(a至z)。
示例1:
输入:"aabcccccaaa"
输出:"a2b1c5a3"
示例2:
输入:"abbccd"
输出:"abbccd"
解释:"abbccd"压缩后为"a1b2c2d1",比原字符串长度更长
提示:
字符串长度在[0, 50000]范围内。
解题思路1
暴力解题
有个细节,i+1超过S.length时,S[i+1]值为undefined,也能比较
var compressString = function(S) {
var string = '';
var number = 1;
for(var i=0;i<S.length;i++){
if(S[i] == S[i+1]){
number++;
}else{
string += S[i] + number;
number = 1;
}
}
return string.length>=S.length?S:string
}
解题思路2
正则表达式
var compressString = function(S) {
let res = ''
let sReg = S.match(/([A-z])\1*/g)
if(sReg){
sReg.forEach(e=>{
res += e.charAt(0)+e.length
})
}
return res.length < S.length ? res : S
};
注释
var s = 'aaaaacccccbbbbb';
var a = s.split('');
a.sort();
s = a.join('');
var pattern = /(\w)\1*/g; ==> 这里\1是什么意思?如果不写这个会怎样?
var ans = s.match(pattern);
ans.sort(function(a, b) {
return a.length < b.length;
});;
console.log(ans[0][0] + ': ' + ans[0].length);
有\1的情况下ans的值为:
["aaaaa","bbbbb","ccccc"]
没有\1的情况下ans的值为:
["aaaaabbbbbccccc"]
如果是\2或者\3呢?
ans值为:["a","a","a","a","a","b","b","b","b","b","c","c","c","c","c"]
正则表达式中的小括号"()"。是代表分组的意思。
如果再其后面出现\1则是代表与第一个小括号中要匹配的内容相同。
注意:\1必须与小括号配合使用
解题思路3
双指针法
var compressString = function(S) {
let res = ""
let i = 0, j = 0
while (j < S.length - 1) {
if (S[j] !== S[j + 1]) {
res += S[i] + (j+1 - i)
i = j+1
}
j++
}
res += S[i] + (j - i + 1)
return res.length < S.length ? res : S
};
旋转矩阵
给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。
不占用额外内存空间能否做到?
示例 1:
给定 matrix =
[ [1,2,3],
[4,5,6],
[7,8,9] ],
原地旋转输入矩阵,使其变为:
[ [7,4,1],
[8,5,2],
[9,6,3] ]
示例 2:
给定 matrix =
[ [ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16] ],
原地旋转输入矩阵,使其变为:
[ [15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11] ]
解题思路1
暴力解题
经过观察:对于矩阵中第 i行的第 j 个元素,在旋转后,它出现在倒数第 i列的第 j个位置
var rotate = function(matrix) {
var n = matrix.length;
const matrix_new = new Array(n).fill(0).map(() => new Array(n).fill(0));
for(var i=0;i<n;i++){
for(var j=0;j<n;j++){
matrix_new[j][n-i-1] = matrix[i][j];
}
}
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
matrix[i][j] = matrix_new[i][j];
}
}
};
解题思路2
原地旋转
与暴力解题方法相似,核心就是利用对于矩阵中第 i行的第 j 个元素,在旋转后,它出现在倒数第 i列的第 j个位置
var rotate = function(matrix) {
const n = matrix.length;
for (let i = 0; i < Math.floor(n / 2); ++i) {
for (let j = 0; j < Math.floor((n + 1) / 2); ++j) {
const temp = matrix[i][j];
matrix[i][j] = matrix[n - j - 1][i];
matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
matrix[j][n - i - 1] = temp;
}
}
};
解题思路3
倒序倒置法
1 2 3
4 5 6
7 8 9
1.将矩阵转置—>
1 4 7
2 5 8
3 6 9
2.将每一行row倒序reverse —>
7 4 1
8 5 2
9 6 3
var rotate = function(matrix) {
const n = matrix.length
for(let i = 0; i < n; i++) {
for(let j = i + 1; j < n; j++) {
const temp = matrix[i][j]
matrix[i][j] = matrix[j][i]
matrix[j][i] = temp
}
matrix[i] = matrix[i].reverse()
}
return matrix
};
零矩阵
编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。
示例 1:
输入:
[ [1,1,1],
[1,0,1],
[1,1,1] ]
输出:
[ [1,0,1],
[0,0,0],
[1,0,1] ]
示例 2:
输入:
[ [0,1,2,0],
[3,4,5,2],
[1,3,1,5] ]
输出:
[ [0,0,0,0],
[0,4,5,0],
[0,3,1,0] ]
解题思路1
set()数组去重 循环清零
利用将值为0的行和列进行存储,从而根据行和列进行相应的数组清零
时间复杂度O(m*n),空间复杂度O(n+n)
var setZeroes = function(matrix) {
var m = matrix.length;
var n = matrix[0].length;
var rows = new Set();
var cols = new Set();
for(var i=0;i<m;i++){
for (var j=0;j<n;j++){
if(matrix[i][j] == 0){
rows.add(i);
cols.add(j);
}
}
};
//for in总是得到对像的key或数组、字符串的下标,
//而for of和forEach一样,是直接得到值,for of不能对对象使用
for(var i of rows){
for(var j=0;j<n;j++){
matrix[i][j] = 0
}
};
for(var i=0;i<m;i++){
for(var j of cols){
matrix[i][j] = 0
}
};
};
解题思路2
使用标记数组
原理与上面的类似。但是效率上低一些,因为Set有去重。
时间复杂度O(mn),空间O(m+n)
var setZeroes = function(matrix) {
const m = matrix.length, n = matrix[0].length;
const row = new Array(m).fill(false);
const col = new Array(n).fill(false);
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (matrix[i][j] === 0) {
row[i] = col[j] = true;
}
}
}
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (row[i] || col[j]) {
matrix[i][j] = 0;
}
}
}
};
解题思路3
两个标记变量
原理:将第一列和第一列单独设置标志位,从第二行和第二列开始遍历,若存在0值,将所在的第一行和第一列对应的位置置0,根据第一行和第一列0值情况进行遍历清零,最后若第一行或第一列标志位为true,则相应的第一行或第一列清零
时间复杂度:O(mn),空间复杂度:O(1)
var setZeroes = function(matrix) {
const m = matrix.length, n = matrix[0].length;
let flagCol0 = false, flagRow0 = false;
for (let i = 0; i < m; i++) {
if (matrix[i][0] === 0) {
flagCol0 = true;
}
}
for (let j = 0; j < n; j++) {
if (matrix[0][j] === 0) {
flagRow0 = true;
}
}
for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
if (matrix[i][j] === 0) {
matrix[i][0] = matrix[0][j] = 0;
}
}
}
for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
if (matrix[i][0] === 0 || matrix[0][j] === 0) {
matrix[i][j] = 0;
}
}
}
if (flagCol0) {
for (let i = 0; i < m; i++) {
matrix[i][0] = 0;
}
}
if (flagRow0) {
for (let j = 0; j < n; j++) {
matrix[0][j] = 0;
}
}
};
解题思路4
使用一个标记变量
原理::将第一列设置标志位,从第一行第二列开始遍历,若存在0值,将所在的第一行和第一列对应的位置置0,根据第一行和第一列0值情况进行遍历清零(此时清零应该从最后一行第二列开始,防止从第一行有0值,开始时直接将第一行清零,导致后续成为0阵),最后若第一列标志位为true,则相应的第一列清零
时间复杂度O(mn) 空间复杂度O(1)
var setZeroes = function(matrix) {
const m = matrix.length, n = matrix[0].length;
let flagCol0 = false;
for (let i = 0; i < m; i++) {
if (matrix[i][0] === 0) {
flagCol0 = true;
}
for (let j = 1; j < n; j++) {
if (matrix[i][j] === 0) {
matrix[i][0] = matrix[0][j] = 0;
}
}
}
for (let i = m - 1; i >= 0; i--) {
for (let j = 1; j < n; j++) {
if (matrix[i][0] === 0 || matrix[0][j] === 0) {
matrix[i][j] = 0;
}
}
if (flagCol0) {
matrix[i][0] = 0;
}
}
};
字符串轮转
字符串轮转。给定两个字符串s1和s2,请编写代码检查s2是否为s1旋转而成(比如,waterbottle是erbottlewat旋转后的字符串)。
示例1:
输入:s1 = "waterbottle", s2 = "erbottlewat"
输出:True
示例2:
输入:s1 = "aa", s2 = "aba"
输出:False
提示:
字符串长度在[0, 100000]范围内。
解题思路1
暴力解题
字符串轮转就是前面第一位“消失”,出现在尾部当某次走马灯显示的内容等于s2的时候,就可以说s2是s1旋转而成
var isFlipedString = function(s1, s2) {
if (s1.length !== s2.length) return false
if (s1 === s2) return true // ''和''也是旋转
let s = ''
for (let i = 0, len = s1.length; i < len; i++) {
s = s1.slice(i) + s1.substring(0, i)
if (s === s2) return true
}
return false
};
解题思路2
当s2是s1旋转而成的话,s2 + s2 一定包含 s1
var isFlipedString = function(s1, s2) {
return s1.length === s2.length && (s2 + s2).includes(s1)
}
太妙了!