算法题总结
1反转字符串中的单词III
给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
示例:
输入:"Let's take LeetCode contest" 输出:"s'teL ekat edoCteeL tsetnoc"
提示:
在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。
//第一种解法
export default (str) => {
return str.split(' ').map(item => {
return item.split('').reverse().join('')
}).join(' ')
}
//第二种解法,把上面的split(' ')换为正则表达式
export default (str) => {
return str.split(/\s/g).map(item => {
return item.split('').reverse().join('')
}).join(' ')
}
//第三种解法
export default (str) => {
return str.match(/[\w']+/g).map(item => {
return item.split('').reverse().join('')
}).join(' ')
}
2计数二进制子串
给定一个字符串 s,计算具有相同数量 0 和 1 的非空(连续)子字符串的数量,并且这些子字符串中的所有 0 和所有 1 都是连续的。
重复出现的子串要计算它们出现的次数。
示例 1 :
输入: "00110011" 输出: 6 解释: 有6个子串具有相同数量的连续1和0:“0011”,“01”,“1100”,“10”,“0011” 和 “01”。
请注意,一些重复出现的子串要计算它们出现的次数。
另外,“00110011”不是有效的子串,因为所有的0(和1)没有组合在一起。 示例 2 :
输入: "10101" 输出: 4 解释: 有4个子串:“10”,“01”,“10”,“01”,它们具有相同数量的连续1和0。
提示:
s.length 在1到50,000之间。 s 只包含“0”或“1”字符。
var countBinarySubstrings = function (s) {
let all = 0;
//匹配字符串,拿出连续的0和1的字段,返回数组
let m = s.match(/([1]+)|(0)+/g);
if (m.length > 1) {
//循环,相邻比较
for (let i = 0; i < m.length - 1; i++) {
all += Math.min(m[i].length, m[i+1].length);
}
}
return all;
};
3电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按任意顺序返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23" 输出:["ad","ae","af","bd","be","bf","cd","ce","cf"] 示例 2:
输入:digits = "" 输出:[] 示例 3:
输入:digits = "2" 输出:["a","b","c"]
提示:
0 <= digits.length <= 4 digits[i] 是范围 ['2', '9'] 的一个数字。
export default (str) => {
//建立电话号码键盘映射
let map = ['', '', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz'];
//把输入字符串按单字符串分隔变成数组,234 => [2, 3, 4]
let num = str.split('');
//保存键盘映射后的字母内容,如23 => ['abc', 'def']
let code = [];
num.forEach(item => {
if (map[item]) {
code.push(map[item]);
}
});
let comb = (arr) => {
//临时变量用来保存前两个组合的结果
let tmp = [];
//最外层的循环是遍历第一个元素,里层的循环是遍历第二个元素
for (let i = 0, il = arr[0].length; i < il; i++) {
for (let j = 0, jl = arr[1].length; j < jl; j++) {
tmp.push(`${arr[0][i]}${arr[1][j]}`);
}
}
//把arr的前两个用循环后的临时变量tmp取代
arr.splice(0, 2, tmp);
if (arr.length > 1) {
//递归
comb(arr);
}else {
return arr[0];
}
//视频里这里是这么写的,我感觉应该像上面一样写更简化
//if (arr.length > 1) {
////递归
//comb(arr);
//}else {
//return tmp;
//}
//return arr[0];
//截止到这,以上是视频的
}
return comb(code);
}
解题思路如图:
4卡牌分组
给定一副牌,每张牌上都写着一个整数。
此时,你需要选定一个数字 X,使我们可以将整副牌按下述规则分成 1 组或更多组:
每组都有 X 张牌。 组内所有的牌上都写着相同的整数。 仅当你可选的 X >= 2 时返回 true。
示例 1:
输入:[1,2,3,4,4,3,2,1] 输出:true 解释:可行的分组是 [1,1],[2,2],[3,3],[4,4] 示例 2:
输入:[1,1,1,2,2,2,3,3] 输出:false 解释:没有满足要求的分组。 示例 3:
输入:[1] 输出:false 解释:没有满足要求的分组。 示例 4:
输入:[1,1] 输出:true 解释:可行的分组是 [1,1] 示例 5:
输入:[1,1,2,2,2,2] 输出:true 解释:可行的分组是 [1,1],[2,2],[2,2]
提示:
1 <= deck.length <= 10000 0 <= deck[i] < 10000
//视频里的解析答案
export default (arr) => {
//对这副牌进行排序,升序、降序都可以
arr.sort((a, b) => a-b);
let min = Number.MAX_SAFE_INTEGER;
let dst = [];
let result = true;
for (let i = 0, len = arr.length, tmp = []; i < len; i++) {
tmp.push(arr[i]);
for (let j = i + 1; j < len - 1; j++) {
if (arr[i] === arr[j]) {
tmp.push(arr[j]);
}else {
if (min > tmp.length) {
min = tmp.length;
}
//这里一定要这么写,因为数组是引用类型,
//直接dst.push(tmp)的话,最后的结果都会被最终的tmp取代
dst.push([].concat(tmp));
//临时数组进行清空
//JS高程里有写,对数组的清空应该用数组名.length = 0来清空,
//不应该是数组名 = []
tmp.length = 0;
//下面这句也很关键
i = j;
break;
}
}
}
//注意,下面为什么用every不用forEach,
//因为forEach不支持跳出,不支持循环的return、break功能
dst.every(item => {
if (item.length % min != 0) {
result = false;
return false;
}
})
return result;
}
//我想到的解析答案
export default (arr) => {
//对这副牌进行排序,升序、降序都可以
arr.sort((a, b) => a-b);
let min = Number.MAX_SAFE_INTEGER;
let result = true;
//数组先转换为字符串,再匹配字符串,拿出连续的字段,返回数组
let dst = arr.join('').match(/([0]+)|(1)+|(2)+|(3)+|(4)+|(5)+|(6)+|(7)+|(8)+|(9)+/g);
//寻找最小组
for (i = 0, len = dst.length; i < len; i++) {
if (dst[i].length < min) {
min = dst.length;
}
dst.every(item => {
if (item.length % min != 0) {
result = false;
return false;
}
})
return result;
}
5种花问题
假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。
给你一个整数数组 flowerbed 表示花坛,由若干 0 和 1 组成,其中 0 表示没种植花,1 表示种植了花。另有一个数 n ,能否在不打破种植规则的情况下种入 n 朵花?能则返回 true ,不能则返回 false。
示例 1:
输入:flowerbed = [1,0,0,0,1], n = 1 输出:true 示例 2:
输入:flowerbed = [1,0,0,0,1], n = 2 输出:false
提示:
1 <= flowerbed.length <= 2 * 104 flowerbed[i] 为 0 或 1 flowerbed 中不存在相邻的两朵花 0 <= n <= flowerbed.length
export default (arr, n) => {
//计数器
let max = 0;
for (let i = 0, len = arr.length - 1; i < len; i++) {
if (arr[i] ===0) {
if (i === 0 && arr[1] ===0) {
max++;
i++;
}else if (arr[i - 1] === 0 && arr[i + 1] === 0) {
max++;
i++;
}
}
}
return max >= n;
}
6格雷编码
格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。
给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。即使有多个不同答案,你也只需要返回其中一种。
格雷编码序列必须以 0 开头。
示例 1:
输入: 2
输出: [0,1,3,2]
解释:
00 - 0
01 - 1
11 - 3
10 - 2
对于给定的 n,其格雷编码序列并不唯一。 例如,[0,2,3,1] 也是一个有效的格雷编码序列。
00 - 0
10 - 2
11 - 3
01 - 1
示例 2:
输入: 0
输出: [0]
解释: 我们定义格雷编码序列必须以 0 开头。 给定编码总位数为 n 的格雷编码序列,其长度为 2n。当 n = 0 时,长度为 2的0次方 = 1。 因此,当 n = 0 时,其格雷编码序列为 [0]。
export default (n) => {
//递归函数,用来算输入为n的格雷编码序列
let make = (n) => {
if (n == 1) {
return ['0', '1'];
}else {
let prev = make(n - 1);
let result = [];
let max = Max.pow(2, n) -1;
for (let i = 0, len = pre.length; i < len; i++) {
result[i] = `0${pre[i]}`;
result[max - i] = `1${pre[i]}`;
}
return result;
}
}
return make(n);
}
解析图例
第一行为0和1并且按照中间区分,接下来的行按照中间对称,依次递归
7重复的子字符串
给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成,给定的字符串只含有小写英文字母,并且长度不超过10000。
示例1:
输入:"abab"
输出:True
解释:可由子字符串"ab"重复两次构成。
示例2:
输入:"aba"
输出:False
示例3:
输入:"abcabcabc"
输出:True
export default (str) => {
var reg = /^(\w+)\1+$/;
return reg.test(str);
}
8正则表达式匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
示例 1:
输入:s = "aa" p = "a" 输出:false 解释:"a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:s = "aa" p = "a*" 输出:true 解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:
输入:s = "ab" p = "." 输出:true 解释:"." 表示可匹配零个或多个('*')任意字符('.')。
示例 4:
输入:s = "aab" p = "cab" 输出:true 解释:因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:
输入:s = "mississippi" p = "misisp*." 输出:false
提示:
0 <= s.length <= 20
0 <= p.length <= 30
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
保证每次出现字符 * 时,前面都匹配到有效的字符
export default (str, mode) => {
//对模式变量进行正则筛选
//筛选出来的是一个数组
let modeArr = mode.match(/([a-z.]\*)|([a-z]+(?=(a-z.)\*|$))/g);
let cur = 0;
let strLen = str.length;
for (let i = 0, len = modeArr.length; i < len; i++) {
//对于模式分为三类 .*|a*|cdef
m = modeArr[i].split('');
if (m[1] === '*') {
if (m[0] === '.') {
cur = strlen;
break;
}else {
while (str[cur] === m[0]) {
cur++;
}
}
}else {
for (let j = 0, jl = m.length; j < jl; j++) {
if (m[j] !== str[cur]) {
return false;
}else {
cur++;
}
}
}
}
return cur === strlen;
}
9冒泡排序
时间复杂度:运行的次数;
空间复杂度:占用的内存
export default (arr) => {
//冒泡排序
//循环次数
for (let i = arr.length - 1; i > 0; i++) {
//每次循环从最开始到结束
for (let j = 0; j < i; j++) {
tmp = arr[j];
if (tmp > arr[j + 1]) {
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
return arr;
}
10选择排序
function selectionSort (arr) {
//选择排序
var len = arr.length;
var minIndex, temp;
for (var i = 0; i < len - 1; i++) {
minIndex = i;
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
[十大经典排序算法(动图演示) - 一像素 - 博客园 (cnblogs.com)](十大经典排序算法(动图演示) - 一像素 - 博客园 (cnblogs.com))
10补--快速排序
从数组中选择一个元素作为基准点; 排序数组,所有比基准值小的元素摆放在左边,而大于基准值的摆放在右边。每次分割结束以后基准值会插入到中间去; 最后利用递归,将摆放在左边的数组和右边的数组在进行一次上述的1和2操作。
var quickSort = function (arr) {
if (arr.length <= 1) {
return arr;
}
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(privotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < piovt) {
left.push(arr[i]);
}else {
right.push(arr[i]);
}
}
return quickSort(left).concat([privot], quickSort(right));
}
11最大间距
给定一个无序的数组,找出数组在排序之后,相邻元素之间最大的差值。
如果数组元素个数小于 2,则返回 0。
示例 1:
输入: [3,6,9,1] 输出: 3 解释: 排序后的数组是 [1,3,6,9], 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3。
示例 2:
输入: [10] 输出: 0 解释: 数组元素个数小于 2,因此返回 0。
说明:
你可以假设数组中所有元素都是非负整数,且数值在 32 位有符号整数范围内。
请尝试在线性时间复杂度和空间复杂度的条件下解决此问题。
//第一种方法,常规做法export default (arr) => { //如果数组长度小于2返回0 if (arr.length < 2) { return 0; } //排序 arr.sort(); //用它来保存相邻元素的最大差值 let max = 0; for (let i = 0, len = arr.length, tmp; i < len; i++) { tmp = arr[i + 1] - arr[i]; if (tmp < max) { max = tmp; } } return max;}//第二种方法,利用冒泡排序export default (arr) => { if (arr.length < 2) { return 0; } let max = 0; let len = arr.length - 1; let space; //遍历次数这里注意,i > 0,对应最后返回那里,特别重要,否则会漏解 for (let i = len, tmp; i > 0; i--) { for (let j = 0; j < i; j) { tmp = arr[j]; if (tmp < arr[j + 1]) { arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } //第二轮后才会把最后两个进行差值比较,但是这种情况只能到arr[2] - arr[1], //对arr[1] - arr[0]的值取不到,因为上面冒泡排序的外层遍历i > 0, //所以要在结尾return处进行处理一下 if (i < len) { space = arr[i + 1] - arr[i]; if (space > max) { max = space; } } } //要在这里处理一下arr[1] - arr[0]这种情况,特别重要 return Math.max(max, arr[1] - arr[0]);}
12按奇偶排序数组II
给定一个非负整数数组 A, A 中一半整数是奇数,一半整数是偶数。
对数组进行排序,以便当 A[i] 为奇数时,i 也是奇数;当 A[i] 为偶数时, i 也是偶数。
你可以返回任何满足上述条件的数组作为答案。
示例:
输入:[4,2,5,7] 输出:[4,5,2,7] 解释:[4,7,2,5],[2,5,4,7],[2,7,4,5] 也会被接受。
提示:
2 <= A.length <= 20000
A.length % 2 == 0
0 <= A[i] <= 1000
export default (arr) => { //进行升序排序 //当然,如果只是排序,完全可以把(a, b) => a - b去掉,直接排序就行 arr.sort((a, b) => a - b); //声明一个空数组用来存储奇偶排序后的数组 let r = []; //记录奇数、偶数下标 let odd = 1; let even = 0; //对数组进行遍历 arr.forEach(item => { if (item % 2 === 1) { r[odd] = item; odd += 2; }else { r[even] = item; even += 2; } }) return r;}
13数组中的第K个最大元素
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2 输出: 5 示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 输出: 4 说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
//第一种解法,常规解法export default (arr, k) => { return arr.sort((a, b) => b - a)[k - 1];}//第二种解法,利用冒泡排序export default (arr, k) => { let len = arr.length - 1; for (let i = len, tmp; i > len - k; i--) { for (let j = 0; j < i; j++) { if (arr[j] > arr[j + 1]) { tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } return arr[len - (k - 1)];}
14缺失的第一个正数
给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
进阶:你可以实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案吗?
示例 1:
输入:nums = [1,2,0] 输出:3 示例 2:
输入:nums = [3,4,-1,1] 输出:2 示例 3:
输入:nums = [7,8,9,11,12] 输出:1 提示:
0 <= nums.length <= 300 -231 <= nums[i] <= 231 - 1
//第一种方法,常规想法export default (arr) => { //过滤掉非正整数 arr = arr.filter(item => item > 0); //正整数数组是不是为空 if (arr.length) { //升序,目的:方便从左到右取最小值arr[0] arr.sort((a, b) => a - b); //如果第一个元素不为1,返回1 if (arr[0] !== 1) { return 1; }else { //从左边开始遍历,只要下一个元素和当前元素差值大于1,则就找到了 for (let i = 0, len = arr.length - 1; i < len; i++) { if (arr[i + 1] - arr[i] > 1) { return arr[i] + 1; } } //如果数组是连续的从1开始的正整数,则最后一个数+1就是所求的值 return arr.pop() + 1; } }else { //如果是个空数组,直接返回1 return 1; }}//第二种方法,选择排序export default (arr) => { arr = arr.filter(item => item > 0); if (arr.length) { //实现选择排序,先拿到最小值,如果第一个元素不是1直接返回1,如果是1,就要比相邻元素差值 for (let i = 0, len = arr.length, min; i < len; i++) { min = arr[i]; for (let j = i + 1; j < len; j++) { if (arr[j] < min) { let c = min; min = arr[j]; arr[j] = c; } } arr[i] = min; //i > 0 表示遍历至少两次了 if (i > 0) { if (arr[i] - arr[i - 1] > 1) { return arr[i - 1] + 1; }else { return arr.pop() + 1; } }else { if (min !== 1) { return 1; } } } }else { return 1; }}
15复原IP地址
给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。
例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效 IP 地址。
示例 1:
输入:s = "25525511135" 输出:["255.255.11.135","255.255.111.35"] 示例 2:
输入:s = "0000" 输出:["0.0.0.0"] 示例 3:
输入:s = "1111" 输出:["1.1.1.1"] 示例 4:
输入:s = "010010" 输出:["0.10.0.10","0.100.1.0"] 示例 5:
输入:s = "101023" 输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
提示:
0 <= s.length <= 3000 s 仅由数字组成
//第一种方法(B站的视频)export default (s) => { var ret = []; //特殊情况 //s的长度小于4(4*1)或者大于12(4*3),那么直接返回空数组 if (s.length < 4 || s.length > 12) { return ret; } //这个函数用来判断传入的string转换为数字是否符合ip的(0, 255)的要求 var isValid = (s) => { //单个整数都可以,0也可以,但两位数04就不可以了 if (s.length === 1) { return true; }else { //088这样的字符串是不符合要求的 if (s[0] === '0') { return false; } //也可以是if (+s < = 255) //因为字符型字符串,前面加上一个+,就会将这个字符串转换为数字 if (Number(s) <= 255) { return true; } } } //定义一个深度探索的函数 //s是原字符串,P是下标指针,str是已经组成的字符串,cnt是已经存在的几组数字 var def = (s, p, str, cnt) => { if (cnt == 4 && p == s.length) { ret.push(str); return; } //cnt = 4,但是p却没走到末尾,p走到末尾但是cnt却没达到4,这两种都不行,直接放弃 if (cnt == 4 || p == s.length) { return; } //i可以取1,2,3 for (let i = 1; i < 4; i++) { //如果指针已经走超过了末尾,直接跳出循环 if (p + i > s.length) { break; } let cutStr = s.slice(p, p + i); if (isValid(cutStr)) { //如果指针没到末尾则不需要加上'.' dfs(s, p + i, str + (p + i == s.length ? curStr : curStr + '.'), cnt + 1); }else { continue; } } } dfs(s, 0, '', 0); return ret;}
16串联所有单词的子串
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例 1:
输入: s = "barfoothefoobarman", words = ["foo","bar"] 输出:[0,9] 解释: 从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。 输出的顺序不重要, [9,0] 也是有效答案。 示例 2:
输入: s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"] 输出:[]
export default (s, words) { if (!words || !words.length) return []; let wordLen = words[0].length; let allWordsLen = wordLen * words.length; let ans = [], wordMap = {}; //遍历数组,改为Map,如果不存在则为1,如果存在则加1 for (let w of words) { wordMap[w] ? wordMap[w]++ : wordMap[w] = 1; } for (let i = 0; i < s.length - allWordsLen + 1; i++) { //把wordMap复制给wn let wn = Object.assign({}, wordMap); //遍历查验Map, //i + allWordsLen - wordLen +1表示从i开始到整个数组.length前一个wordLen的后一位 for (let j = i; j < i + allWordsLen - wordLen +1; j += wordLen) { let w = s.slice(j, j + wordLen); if (wm[w]) { wm[w]--; }else { break; } } if (Object.values(wm).every(n => n === 0)) ans.push(i); } return ans;}