1.颜色分类
题目:
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?
思路:首先,用map记录每个数出现的次数,然后按照顺序覆盖原数组
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var sortColors = function(nums) {
const map = new Map();
for (const i of nums) {
map.set(i, (map.get(i) || 0) + 1);
}
nums.splice(0, Infinity);
for (let i = 0; i < 3; i++) {
for (let j = 0, l = map.get(i); j < l; j++) {
nums.push(i);
}
}
};
如果要用常数空间来实现的话,可以用指针法。因为这题的颜色数只有3个,我们用双指针,左指针指向0,右指针放在最右边。依次遍历数组的元素,遇到2,那么我们和右指针的数字交换,同时右指针向左移动一;如果遇到0,那么和左指针的数字交换,同时左指针向右移动一。直到遇到右指针。
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var sortColors = function(nums) {
const l = nums.length;
let start = 0,
end = l - 1;
let i = 0;
while (i <= end) {
if (nums[i] == 2) {
[nums[i], nums[end]] = [nums[end], nums[i]];
end--;
} else if (!nums[i]) {
[nums[i], nums[start]] = [nums[start], nums[i]];
start++;
i++;
} else {
i++;
}
}
};
2.组合
题目:给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
思路:动态规划,从空数组开始,依次遍历原数组的元素加入到新数组里。用一个变量记录当前遍历到了哪个数上,每次都在这个数右边部分取值加入到新数组里。如果新数组的长度等于K,就将这个新数组加入到结果里
/**
* @param {number} n
* @param {number} k
* @return {number[][]}
*/
var combine = function(n, k) {
var result = [];
var subresult = [];
function combineSub(start,subresult){
//terminator
if(subresult.length == k){
result.push(subresult.slice(0));
return;
}
var len = subresult.length;
for(var i= start;i<=n-(k-len)+1;i++){
subresult.push(i);
combineSub(i+1,subresult);
subresult.pop();
}
}
combineSub(1,subresult);
return result;
};
换个思路。从N个数中取K个数的数量,等于从N-1个数中取K个数,以及从N-1个数中取K-1个数的和。(后一种结果,是因为默认把第K个数放进结果里面了),直到N和K相等。
/**
* @param {number} n
* @param {number} k
* @return {number[][]}
*/
var combine = function(n, k) {
var result = [];
var subresult = [];
if( n==k || k == 0){
var tmp = [];
for(var i = 1;i<=k;i++){
subresult.push(i);
}
tmp.push(subresult);
return tmp;
}
var result = combine(n-1,k-1);
result.forEach((arr) => arr.push(n));
var tmp = combine(n-1,k);
return result.concat(tmp);
};
3.子集
题目:
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
思路:和上一题类似,只不过K是从0到N而已。
/**
* @param {number[]} nums
* @return {number[][]}
*/
var subsets = function (nums) {
let n = nums.length;
let tmpPath = [];
let res = [];
let backtrack = (tmpPath,start) => {
res.push(tmpPath);
for(let i = start;i < n;i++){
tmpPath.push(nums[i]);
backtrack(tmpPath.slice(),i+1);
tmpPath.pop();
}
}
backtrack(tmpPath,0);
return res;
};
4.单词搜索
题目:
给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
思路:遍历每个字符串,判断当前字符是否和单词第一个字符相等,如果相等,进入递归的方法。
递归的方法,其实就是从上下左右四个方向分别判断字符是否和剩下的单词的第一字符相等。为了防止重复的字符,需要用set来记录当前这种可能的时候用过的字符
/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function (board, word) {
const h = board.length;
if (!h) return false;
const set = new Set();
const w = board[0].length;
for (let i = 0; i < h; i++) {
for (let j = 0; j < w; j++) {
if(board[i][j]!==word[0])continue
const res = existAA(board, word, i, j, set);
if (res) return true;
set.clear();
}
}
return false
};
var existAA = function (board, word, i, j, set) {
if (!board[i] || !board[i][j]) return false;
if (set.has(`${i}-${j}`)) return false;
if (board[i][j] !== word[0]) return false;
const s = word.slice(1);
const newSet=new Set([...set])
newSet.add(`${i}-${j}`);
if (!s.length) return true;
return (
existAA(board, s, i - 1, j, newSet) ||
existAA(board, s, i + 1, j, newSet) ||
existAA(board, s, i, j - 1, newSet) ||
existAA(board, s, i , j + 1, newSet)
);
};
优化:这种方式需要不断实例化set,比较消耗空间和性能。那么,我们可以用原矩阵去实现这个功能。我们把用过的字符都变成null,在完成这轮递归之后还原
/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function(board, word) {
if(board.length===0) return false
if(word.length===0) return true
let row = board.length
let col = board[0].length
for(let i=0;i<row;i++){
for(let j=0;j<col;j++){
const ret = find(i,j,0)
if (ret) return true
}
}
return false
function find(i,j,cur){
if(i>=row||i<0) return false
if(j>=col||j<0) return false
const letter = board[i][j]
if(letter!==word[cur]) return false
if(cur===word.length-1) return true
board[i][j]=null
const ret = find(i+1,j,cur+1)||find(i-1,j,cur+1)||find(i,j+1,cur+1)||find(i,j-1,cur+1)
board[i][j]=letter
return ret
}
};
5.删除排序数组中的重复项
题目:
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
思路:原地算法,使用常数空间。所以我们要用指针的方式去完成。从左到右遍历,用一个变量记录当前的值,一个变量记录当前值的次数,一个变量记录有效数字的位置。
遍历数组,遇到和当前值不同的元素,次数重置为1;如果遇到和当前值相同的元素,先判断出现的次数。如果之前已经出现过两次,那么就直接跳过。如果没有出现过两次,次数+1.那么回过头来,我们要在遇到和当前值不同的元素的时候,把当前的值赋值给有效数字对应的下标上。
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
const l = nums.length;
if (l < 3) return l;
let left = 0,
right = 0;
let cur = NaN,
count = 0;
while (right < l) {
if (nums[right] === cur && count >= 2) {
} else {
if (nums[right] !== cur) {
cur = nums[right];
count = 1;
} else {
count++;
}
if (left !== right) {
nums[left]= nums[right];
}
left++;
}
right++;
}
return left;
};
另一个方式,就是遇到出现次数两次的元素,删除当前元素即可
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
if (nums.length < 3) return nums.length;
let i=0;
let cur = NaN,
count = 0;
while (i < nums.length) {
if (nums[i] === cur && count >= 2) {
nums.splice(i,1)
i--
} else {
if(nums[i]!==cur){
count=1;
cur=nums[i]
}else{
count++
}
}
i++;
}
return i;
};