个人算法学习打卡笔记
递归
1. 十进制转其他进制
/**
* @description: 进制转换
* @param {number} n 十进制数
* @param {number} d 进制
* @return {string}
*/
function otherByDecimal_1(n, d, s = [], tpl = '0123456789abcdef') {
if (n === 0) return;
otherByDecimal_1(Math.trunc(n / d), d, s);
s.push(tpl.charAt(n % d));
return s.join('');
}
function otherByDecimal_2(n, d, s = '', tpl = '0123456789abcdef') {
if (n === 0) return s;
s = tpl.charAt(n % d) + s;
return otherByDecimal_2(Math.trunc(n / d), d, s);
}
console.log(otherByDecimal_1(31, 16);
console.log(otherByDecimal_2(31, 16);
分治法
1. 求pow(x, y),即x的y次方 T(n) = O(logn)
function pow(x, y) {
function f(half) {
if (half === 0) return 1;
if (half === 1) return x;
if (half === -1) return 1 / x;
return f(half / 2) * f(half / 2);
}
// 处理奇数项
if (y >= 0) {
return y % 2 === 1 ? f(y - 1) * x : f(y);
} else {
return y % 2 === -1 ? f(y + 1) * (1 / x) : f(y);
}
}
console.log(pow(9, -9), Math.pow(9, -9))
2.求fibonacci数列
2.1 普通
超时
function fibonacci(n) {
if (n <= 0) return 0
if (n <= 2) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
2.2 记忆法
会爆栈
function cacheFibonacci(n) {
const memo = [0, 1, 1];
function fibonacci(n) {
if (n < 0) return 0;
if (memo[n] !== undefined) return memo[n];
return memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);
}
return fibonacci(n);
}
2.3 尾调优化
function fibonacci(n, ac1 = 1, ac2 = 1) {
if (n <= 2) return ac2;
return fibonacci(n-1, ac2, ac1 + ac2);
}
2.4 普通方法
function fibonacci(n){
let n1 = 1, n2 = 1, n3;
for(let i=n; i>2; i--){
n3 = n1
n1 = n2;
n2 = n2 + n3;
}
return n2;
}
栈
基于栈的快速用数组构建栈思想
1.对对碰
function pair(str) {
const stack = [];
const pairArr = {
'{': "}",
'[': ']',
'(': ')'
};
for(let i of str){
const peek = stack[stack.length - 1];
if(pairArr[peek] === i){
stack.pop()
}else{
stack.push(i)
}
}
return stack.length === 0;
}
console.log(pair('[(()]'), pair('([][])'))// false true
2.十进制转二进制
function decimalToBinary(decimal){
const stack = [];
while(decimal > 0){
stack.unshift(decimal % 2);
decimal = Math.floor(decimal / 2);
}
return stack.join('')
}
console.log(decimalToBinary(3)) // 11
3.气温问题
栈顶递减法
根据每日气温列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。
给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
function dailyTemperatures(T){
const stack = [], result = new Array(T.length).fill(0);
for(let [key, val] of T.entries()){
const len = stack.length;
while(stack.length>0 && val > stack[stack.length-1].val){
const cur = stack.pop();
result[cur.key] = key-cur.key;
}
stack.push({key, val});
}
return result;
}
队列
用栈实现队列
class Queue{
constructor(){
this.stack1 = [];
this.stack2 = [];
}
push(val){
this.stack1.push(val);
}
pop(){
while(this.stack2.length <= 0){
while(this.stack1.length){
this.stack2.push(this.stack1.pop());
}
}
return this.stack2.pop()
}
peek(){
while(this.stack2.length <= 0){
while(this.stack1.length){
this.stack2.push(this.stack1.pop());
}
}
const len = this.stack2.length;
return len ? this.stack2[len - 1] : null;
}
empty(){
return !this.stack1.length && !this.stack2.length;
}
}
双端队列滑窗问题
function slideWin(nums, k){
const len = nums.length;
const res = [];
for(let i=0; i<len - 1; i++){
const last = i+k;
if(last > len) break;
const max = Math.max.apply(null, nums.slice(i, last));
res.push(max);
}
return res;
}
function slideWinDoubleQueue(nums, k){
const len = nums.length,
res = [], dequeue = [];
for(let i = 0;i<len;i++){
let dequeueLen = dequeue.length;
while(dequeueLen > 0 && nums[dequeue[dequeueLen - 1]] < nums[i]){
dequeue.pop();//维持递减队列
dequeueLen--;
}
dequeue.push(i); dequeueLen++;
while(dequeueLen && dequeue[0] <= i - k){
dequeue.shift();//去除队列首部不在滑块区间的值
dequeueLen--;
}
if(i >= k-1){ //让第一次滑动到达要求距离
res.push(nums[dequeue[0]]);
}
}
return res;
}
链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。
输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4
function merge(l1, l2){
const head = new ListNode();
let cur = head;
while(l1 && l2){
if(l1.val > l2.val){
cur.next = l2;
l2 = l2.next;
}else{
cur.next = l1;
l1 = l1.next;
}
cur = cur.next;
}
// 剩下的直接衔接在后面
cur.next = l1 === null ? l2 : l1;
return head.next;// 首节点值为undefined
}
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1: 输入: 1->1->2 输出: 1->2 示例 2: 输入: 1->1->2->3->3 输出: 1->2->3
function delRepeat(head){
let cur = head;
while(cur != null && cur.next != null){
if(cur.val === cur.next.val){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
return head;
}
虚拟指针
给定一个排序链表,删除所有含有重复数字的结点,只保留原始链表中 没有重复出现的数字。
示例 1: 输入: 1->2->3->3->4->4->5 输出: 1->2->5 示例 2: 输入: 1->1->1->2->3 输出: 2->3
function delDup(head){
if(!head || !head.next) return head;
const dummy = new ListNode();
// 前面增加一个虚拟节点
dummy.next = head;
let cur = dummy;
while(cur.next && cur.next.next){
if(cur.next.val === cur.next.next.val){
let val = cur.next.val;
while(cur.next && cur.next.val === val){
cur.next = cur.next.next;
//无限循环删除重复的
}
}else{
cur = cur.next;
}
}
return dummy.next;
}
快慢指针
给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
给定一个链表: 1->2->3->4->5, 和 n = 2. 当删除了倒数第二个结点后,链表变为 1->2->3->5. 说明: 给定的 n 保证是有效的。
function removeNthFromEnd(head, n){
const dummy = new ListNode();
dummy.next = head;
let slow = fast = dummy;
while(n > 0){
fast = fast.next;
n--;
}
while(fast.next){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
三指针翻转链表
function reverseList(head){
let pre = null;
let cur = head;
while(cur !== null){
let next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
局部翻转链表输入: 1->2->3->4->5->NULL, m = 2, n = 4 输出: 1->4->3->2->5->NULL
function reverseRange(head, m, n){
let pre,cur,leftHead;
const dummy = new ListNode();
dummy.next = head;
let p = dummy;
for(let i=0;i<m-1;i++){
p=p.next;
}
leftHead = p;//储存前面的衔接点
let start = leftHead.next;//开始翻转点
pre = start;
cur = pre.next;
for(let i=m;i<n;i++){
let next = cur.next;
cur.next = pre;
pre = cur;
cur=next;
}
leftHead.next = pre;// 衔接前面
start.next = cur;//衔接后面
return dummy.next;
}
环形链表
判断一个链表中是否有环
function isCycle(head){
while(head){
if(head.flag){
return true;
}else{
head.flag = true;
head = head.next;
}
}
return false;
}
深度优先搜索
穷举其实就是数的先序遍历
function xxx(入参) {
前期的变量定义、缓存等准备工作
// 定义路径栈
const path = []
// 进入 dfs
dfs(起点)
// 定义 dfs
dfs(递归参数) {
if(到达了递归边界) {
结合题意处理边界逻辑,往往和 path 内容有关
return
}
// 注意这里也可能不是 for,视题意决定
for(遍历坑位的可选值) {
path.push(当前选中值)
处理坑位本身的相关逻辑
path.pop()
}
}
}
1. 先序遍历
常规递归
function pre(root){
const res = [];
function preOrderTraversal(root){
if(!root) return;
res.push(root.val);
if(root.left) preOrderTraversal(root.left);
if(root.right) preOrderTraversal(root.right);
}
preOrderTraversal(root);
return res;
}
栈方法
function preOrderTraversal(root){
const res = [];
if(!root){
return res;
}
const stack = [root];
while(stack.length){
const cur = stack.pop();
res.push(cur.val);
if(cur.right) stack.push(cur.right);
if(cur.left) stack.push(cur.left);
}
return res;
}
2. 中序遍历
常规打印
function mid(root){
const res = [];
function midOrderTraversal(root){
if(!root) return;
if(root.left) midOrderTraversal(root.left);
res.push(root.val);
if(root.right) midOrderTraversal(root.right);
}
midOrderTraversal(root);
return res;
}
栈方式
function midOrderTraversal(root){
const res = [], stack = [];
let cur = root;
while(stack.length || cur){
while(cur){
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
res.push(cur.val);
cur = cur.right;
}
return res;
}
3. 后序遍历
常规打印
function after(root){
const res = [];
function afterOrderTraversal(root){
if(!root) return;
if(root.left) afterOrderTraversal(root.left);
if(root.right) afterOrderTraversal(root.right);
res.push(root.val);
}
afterOrderTraversal(root);
return res;
}
栈方法
function afterOrderTraversal(root){
const res = [];
if(!root) return res;
const stack = [root];
while(stack.length){
const cur = stack.pop();
res.unshift(cur.val);
if(cur.left) stack.push(cur.left);
if(cur.right) stack.push(cur.right);
}
return res;
}
4. 全排列问题
题目描述:给定一个没有重复数字的序列,返回其所有可能的全排列。示例:
输入: [1,2,3] 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
const permute = function(nums){
const len = nums.length;
const curr = [];
const res = [];
const visited = {};
function dfs(nth){
if(nth === len){
res.push(curr.slice());
return;
}
for(let i=0; i<len; i++){
if(!visited[nums[i]]){
visited[nums[i]] = 1;
curr.push(nums[i]);
dfs(nth + 1);
curr.pop();
visited[nums[i]] = 0;
}
}
}
dfs(0);
return res;
}
console.log(permute([1,2,3]))
5. 全结果
题目描述:给定一组不含重复元素的整数数组nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
const subsets = function(nums){
const res = [], len = nums.length, subset = [];
dfs(0);
function dfs(index){
res.push(subset.slice());
for(let i=index;i<len;i++){
subset.push(nums[i]);
dfs(i+1);
subset.pop();
}
}
return res;
}
console.log(subsets([1,2,3]))
6.限定组合问题
题目描述:给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
const combine = function(n, k){
const res = [], subset = [];
dfs(0);
function dfs(index){
if(subset.length === k){
res.push(subset.slice());
return;
}
for(let i=index;i<n;i++){
subset.push(i);
dfs(i+1);
subset.pop();
}
}
return res;
}
console.log(combine(4, 2))
6. 层序遍历问题
- 示例: 二叉树:[3,9,20,null,null,15,7],返回其层次遍历结果: [ [3], [9,20], [15,7] ]
function levelOrder(root){
const res = [];
if(!root) return res;
const queue = [root];
while(queue.length){
const len = queue.length;//记录当前这层的长度
const cur = [];
for(let i=0; i<len; i++){//直接一次性把该层消耗光
const head = queue.shift();
cur.push(head.val);
if(head.left) queue.push(head.left);
if(head.right) queue.push(head.right);
}
res.push(cur);
}
return res;
}
7. 翻转二叉树
const invertTree = function(root){
if(!root) return root;
const {left, right} = root;
if(left){
root.right = left;
invertTree(left);
}
if(right){
root.left = right;
invertTree(right);
}
}
广度优先搜索
1.树的层序遍历
// 广度优先搜素
function BFS(root){
const queue = [];
// 根节点入队
queue.push(root);
// 当队列长度存在
while(queue.length){
const top = queue[0];
console.log(top.val);
// 左子树存在入队
if(top.left){
queue.push(top.left);
}
// 右子树存在入队
if(top.right){
queue.push(top.right);
}
//当前层出队
queue.shift();
}
}
回溯算法
回溯采用试错的方法解决问题。一旦发现当前步骤失败,回溯方法就返回上一个步骤,选择另一种方案继续试错。
全排列问题
function allOrder(arr, cur = [], res = []){
const len = arr.length;
if(len === 0) {
return res.push(cur.slice())
};
for(let i=0; i< len; i++){
allOrder([...arr.slice(0,i), ...arr.slice(i+1)], [...cur, arr[i]], res);
}
return res;
}
console.log(allOrder(['红', '黑', '黄']));
限定组合
function limitCombine(arr, n, cur = [], res = []){
if(cur.length === n){
res.push(cur.slice());
return;
}
const len = arr.length;
for(let i=0; i<len; i++){
limitCombine([...arr.slice(0, i), ...arr.slice(i+1)], n, [...cur, arr[i]], res);
}
return res;
}
console.log(limitCombine(['红', '黑', '黄'], 2));
二维数组棋牌相关
1. 棋牌中是否有某个连续字母的单词
function helper(board, current, row, column) {
if (current.length === 0) return true;
// 确立边界
if (row >= 0 && row < board.length && column >= 0 && column < board[0].length) {
if (board[row][column] === current[0]) {
board[row][column] = ''; //标记为已走过
// 检查上边界有没有下一个字母
if (helper(board, current.slice(1), row - 1, column)) return true;
// 检查下边界有没有下一个字母
if (helper(board, current.slice(1), row + 1, column)) return true;
// 检查左边界有没有下一个字母
if (helper(board, current.slice(1), row, column - 1)) return true;
// 检查右边界有没有下一个字母
if (helper(board, current.slice(1), row, column + 1)) return true;
board[row][column] = current[0]; //复原标记已读的
}
}
return false;
}
function searchWord(board, word) {
const current = word.split(''),
rowLen = board.length,
columnLen = board[0].length;
for (let i = 0; i < rowLen; i++) {
for (let j = 0; j < columnLen; j++) {
if (helper(board, current, i, j)) {
return true;
}
}
}
return false;
}
const board = [
['a', 'c', 'r', 'y', 'l'],
['l', 'w', 'o', 'r', 'i'],
['a', 'f', 'd', 'l', 'c'],
['k', 'e', 'e', 'w', 'e'],
['o', 'd', 'r', 'o', 's']
]
console.log(searchWord(board, 'world'));
2.n皇后问题
// 传入记录的点 行 和 n皇后
function helper(columnPos, rowIndex, n) {
if(rowIndex === n){
console.log(columnPos)
print(columnPos, n);
return;
}
for(let column=0;column<n; column++){
columnPos[rowIndex] = column;//当前行的第几个
if(isVaild(columnPos, rowIndex)){
helper(columnPos, rowIndex + 1, n);
}
}
}
function isVaild(columnPos, rowIndex){
for(let row=0; row<rowIndex; row++){//循环行
if(columnPos[row] === columnPos[rowIndex]) return false;//在同一列
// 在同一对角线
if(Math.abs(columnPos[row] - columnPos[rowIndex]) === rowIndex - row) return false;
}
return true;
}
function print(columnPos, n){
for(let i=0; i<n; i++){
const column = columnPos[i];
const arr = new Array(n).fill('口');
arr.splice(column, 1, '皇');
console.log(arr.join(''));
}
console.log('\n')
}
helper([], 0, 8)
3.数独游戏
class Sudoku{
constructor(board){
this.board = board;
this.n = board.length;
this.len = Math.pow(this.maxRow, 2);
}
/**
* @description:
* @param {number} index 第几个格子
* @return:
*/
helper(row=0, column=0){
if(row>= this.n-1 && column>=this.n-1){
this.board.forEach(e => {
console.log(e.join(' '))
});
console.log('\n');
return;
}
if(!this.board[row][column]){
for(let i=1; i<10; i++){
if(this.isVaild(row, column, i)){
this.board[row][column] = i;
this.helper(...this.getRowColumn(row, column+1));
this.board[row][column] = '';
}
}
}else{
this.helper(...this.getRowColumn(row, column+1));
}
}
getRowColumn(row, column){
if(column >= this.n){
return [row+1, 0]
}
return [row, column]
}
isVaild(row, column, i){
for(let j=0; j<this.n; j++){
if(j === column) continue;
if(this.board[row][j] === i) return false;
}
for(let k=0; k<this.n; k++){
if(k === row) continue;
if(this.board[k][column] === i) return false;
}
let startRow, startColumn, endRow, endColumn;
if(row%3 === 0){
startRow = row;
endRow = row + 2;
}else if(row%3 === 1){
startRow = row - 1;
endRow = row + 1;
}else{
startRow = row - 2;
endRow = row;
}
if(column%3 === 0){
startColumn = column;
endColumn = column + 2;
}else if(column%3 === 1){
startColumn = column - 1;
endColumn = column + 1;
}else{
startColumn = column - 2;
endColumn = column;
}
for(let j=startRow; j<=endRow; j++){
for(let k=startColumn; k<=endColumn; k++){
if(j === row && k === column) continue;
if(this.board[j][k] === i) return false;
}
}
return true;
}
}
const board = [
[4, 1, 0, 0, 0, 7, 8, 5, 0],
[8, 0, 6, 0, 0, 0, 0, 0, 9],
[0, 2, 0, 0, 9, 0, 6, 0, 0],
[0, 0, 4, 0, 0, 0, 0, 1, 2],
[2, 0, 0, 5, 8, 0, 0, 7, 0],
[0, 0, 0, 0, 0, 0, 5, 0, 0],
[0, 0, 0, 7, 0, 2, 0, 0, 0],
[0, 0, 8, 0, 1, 0, 0, 0, 0],
[0, 7, 0, 0, 6, 0, 0, 0, 0],
]
const sudoku = new Sudoku(board);
sudoku.helper()
贪心算法
硬币找零问题
求出找零需要最少硬币的最优解
function getSmallestConis(money){
const coins = [0.05, 0.1, 0.2, 0.5, 1, 2];
const counts= [20, 15, 10, 5, 2, 1];
const total = coins.reduce((total, coin, idx) => {
return total += coin * counts[idx];
}, 0)
if(money > total) return '太大,找不开';
let idx = coins.length - 1;
while(idx >= 0 || money >= coins[0]){
let n = Math.trunc(money/coins[idx]);
if(n>0){
n = n <= counts[idx] ? n : counts[idx];
console.log(`${coins[idx]}元硬币${n}枚`);
money = sub(money, n * coins[idx]);
}
idx--;
}
console.log(`最终剩余${money}元钱没法找`)
}
// 减法防止精度溢出
function sub(num1, num2){
const n1 = String(num1).split('.')[1] || '';
const n2 = String(num2).split('.')[1] || '';
let n = Math.max(n1.length, n2.length);
const level = Math.pow(10, n);
return ((num1*level - num2*level)/level).toFixed(n1.length>n2.length ? n1.length : n2.length);
}
getSmallestConis(2.5512);
活动安排问题
给出每个会议的开始和结束时间,求当天安排最多的会议的最优解
function bubble(startTimeList, endTimeList) {
//对结束时间进行排序
const len = endTimeList.length;
for (let i = 0; i < len; i++) {
for (let j = 0; j < len - i - 1; j++) {
if (endTimeList[j] > endTimeList[j+1]) {
[endTimeList[j], endTimeList[j+1]] = [endTimeList[j+1], endTimeList[j]];
[startTimeList[j], startTimeList[j+1]] = [startTimeList[j+1], startTimeList[j]];
}
}
}
return {
startTimeList,
endTimeList
};
}
function greedy(startTimeList, endTimeList) {
bubble(startTimeList, endTimeList); //进行排序
const len = startTimeList.length;
const tag = [true]; //第一个时间最早的活动加入进去
let curActivityIndex = 0;
for (let i = 1; i < len; i++) {
if (endTimeList[curActivityIndex] <= startTimeList[i]) {
tag.push(true);
curActivityIndex = i;
} else {
tag.push(false);
}
}
//打印结果
tag.forEach((isChoose, idx) => {
if (isChoose) {
console.log([startTimeList[idx], endTimeList[idx]])
}
})
}
// 每个会议的开始时间
const startTimeList = [0, 2, 4, 6, 1, 1, 1, 3, 5, 5, 5];
// 每个会议的结束时间
const endTimeList = [2, 4, 6, 8, 3, 3, 3, 5, 7, 7, 7];
greedy(startTimeList, endTimeList);
动态规划
工人挖金矿问题
找出某个矿必须挖的情况下最大可获取的黄金数
const personNum = 10;
//每个矿的黄金储量
const gold = [400, 500, 200, 300, 350];
//每个矿所需要的工人
const goldPerson = [5, 5, 3, 4, 3];
function getResult(personNum, gold, goldPerson){
let res = [],
pre = [],
n = gold.length; //金矿数
for(let i=0; i<personNum; i++){//初始化
if(i>=goldPerson[0]){//人数大于等于第一个金矿所需人数
pre[i] = gold[0];//获得第一个矿金子
}else{
pre[i] = 0;
}
}
for(let i=0; i<n; i++){//循环金矿
for(let j=0; j<personNum; j++){//循环矿工
if(j<goldPerson[i]){//矿工少于当前金矿所需人数则采矿数为上一次的当前最优
res[j] = pre[j];
}else{
// 要么这个矿不采用上次当前人数最优解
// 要么当前人数-当前所需最少的人 来获取上次最优解 并且加上这次的矿
// 两个获取最大值
res[j] = Math.max(pre[j], pre[j-goldPerson[i]] + gold[i]);
}
}
pre = res;
}
return res;
}
console.log(getResult(personNum, gold.slice(0,1), goldPerson.slice(0,1)))
背包装石头问题
某个背包容量最大装石头量
const stoneWeight = [0,2,3,4,5,6];
const stoneValue = [0,3,4,5,6,7];
const bagCapacity = 10;
// 得到最大值
const getTable = function(stoneWeight, stoneValue, bagCapacity){
const res = [new Array(bagCapacity+1).fill(0)];
const stoneNum = stoneWeight.length;
for(let stone = 1; stone<stoneNum; stone++){//循环石头编号
const cur = [];
const pre = res[res.length - 1]; //结果最后一项作为上次
const w = stoneWeight[stone];//当前石头重量
const v = stoneValue[stone];//当前石头的价值
for(let bag=0; bag<=bagCapacity; bag++){//循环背包容量
if(bag<w){//若背包容量小于当前石头所需容量则装不了,只能取上次
cur[bag] = pre[bag];
}else{//要么装现在的石头 要么不装
//不装当前 和装当前价值对比(装当前剩余容量可以装上次剩余容量的最优值)
cur[bag] = Math.max(pre[bag], v + pre[bag-w]);
}
}
res.push(cur.slice());
}
return res;
}
const table = getTable(stoneWeight, stoneValue, bagCapacity);
function print(table, stoneWeight, stoneValue){
let curRow = table.length -1;//记录当前行
let curColumn = table[0].length - 1;//记录当前列
let curVal = table[curRow][curColumn];//记录当前剩余值
while(curRow){
let preRow = table[curRow -1];//上一行
if(curVal === preRow[[curColumn]]){//当前行和上一行相等则肯定没装当前编号石头
console.log(`背包没有装${curRow}号石头`);
curRow--;
}else{//不想等则装了 然后减去石头的值 查找上一行的价值
console.log(`背包装有${curRow}号石头,石头重量为${stoneWeight[curRow]},石头价值为${stoneValue[curRow]}`);
curVal -= stoneValue[curRow];
const curColumn = preRow.find(e => e === curVal);
curRow--;
}
}
}
print(table, stoneWeight, stoneValue);
公共子序列
s1='abcd'和s2='becd'两个字符中同时出现的字符且字符的先后顺序一样,则为公共子序列。比如'bcd',其中最长的公共子序列被叫做最长公共子序列
假如字符串
A='a0,a1,a2...am';
B='b0,b1,b2...bn';
最长公共子序列为
Z='z0,z1,z2...zk';
如果am=bn=zk则可以推导出'a0,a1,a2...a(m-1)'和'b0,b1,b2...b(n-1)'的最长公共子序列为'z0,z1,z2...z(k-1)';
如果am!=bn,则zk!=am;'a0,a1,a2...a(m-1)'和'b0,b1,b2...bn'的最长公共子序列为'z0,z1,z2...zk';
如果am!=bn,则zk!=bn;'a0,a1,a2...am'和'b0,b1,b2...b(n-1)'的最长公共子序列为'z0,z1,z2...zk';
通过上面我们可以推导出求 C[i, j]的最长公共子序列
i为字符A的最后一项
j为字符B的最后一项
if(i===0 || j===0){ 最长子序列为零 }
if(i === j){ 最长子序列为 C[i-1, j-1] + 1; }
if(i !== j){ 最长公共子序列为 max(C[i-1, j], C[i, j-1]) }
const getTable = (s1, s2) => {
const len1 = s1.length, len2 = s2.length;
let max = 0;
if(len1 === 0 || len2 === 0) return max;
const table = [];
for(let i=0; i<len1; i++){
table.push([]);
const cur = table[table.length - 1];
for(let j=0; j<len2; j++){
if(s1[i] === s2[j]){
cur[j] = ((table[i - 1] || [])[j-1] || 0) + 1;//为了解决没有首部空行来垫
}else{
cur[j] = Math.max(((table[i - 1] || [])[j] || 0), ((table[i] || [])[j-1] || 0))
}
}
}
return table;
}
const s1 = '13456778', s2 = '357486782';
const table = getTable(s1, s2);
双指针法
有序数组联想双指针对撞指针
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。 说明: 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例: 输入: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3 输出: [1,2,2,3,5,6]
function f(arr1, n, arr2, m) {
let i = n - 1,
j = m - 1,
k = n + m - 1;
while (i >= 0 && j >= 0) {
if (arr1[i] > arr2[j]) {
arr1[k] = arr1[i];
k--;
i--;
} else {
arr1[k] = arr2[j]
k--;
j--;
}
}
while (j >= 0) {
arr1[k] = arr2[j];
k--;
j--;
}
}
真题描述:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 注意:答案中不可以包含重复的三元组。
真题描述:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 注意:答案中不可以包含重复的三元组。
const nums = [-1, 0, 1, 2, -1, -4];
function threeSum(nums) {
// 排序
nums.sort((a, b) => {
return a - b;
})
const result = [];
const len = nums.length;
for (let i = 0; i < len - 2; i++) {//最后两个元素不用去
let j = i + 1,
k = len - 1;
if (i > 0 && nums[i] === nums[i - 1]) {
continue; //去重
}
while (j < k) {
const diff = nums[j] + nums[k] + nums[i];
if (diff > 0) {
k--;
while (j < k && nums[k] === nums[k + 1]) {
k--; //去重
}
} else if (diff < 0) {
j++;
while (j < k && nums[j] === nums[j - 1]) {
j++; //去重
}
} else {
result.push([nums[i], nums[j], nums[k]]);
j++;
k--;
while (j < k && nums[k] === nums[k + 1]) {
k--; //去重
}
while (j < k && nums[j] === nums[j - 1]) {
j++; //去重
}
}
}
}
return result;
}
threeSum(nums)
//[ [ -1, -1, 2 ], [ -1, 0, 1 ] ]
给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
function validPalindrome(s){
s = s.replace(/\s+/g, '');
const len = s.length;
let [i, j] = [0, len-1];
while(i<j && s[i] === s[j]){
i++;
j--;
}
if(isPalindrome(i+1, j)){
return true;
}
if(isPalindrome(i, j-1)){
return true;
}
function isPalindrome(st, sd){
while(st<sd){
if(s[st] !== s[sd]) return false;
st++;
sd--;
}
return true;
}
return false;
}
其他
1.求是否为素数 T(n) = O(logn)
function isPrime(n){
if(n <= 1) return false;
const N = Math.floor(Math.sqrt(n));
for(let i = 2; i<=N; i++){
if(n%i === 0){
return false;
}
}
return true;
}
2.求数组的连续子数组,和是否能被整除
function f(arr, N){
let isPass = false;
for(let i=0;i<=arr.length;i++){
for(let j=i+1;j<=arr.length;j++){
const remain = arr.slice(i, j).reduce((total, val) => {
return total + val
}) % N
if(remain === 0) return true;
}
}
return false;
}
console.log(f([1,2,3], 6)) //true
3.投射法
const students = [{
score: 70,
name: 'xiaoming',
}, {
score: 80,
name: 'xiaowang',
}, {
score: 50,
name: 'xiaohong',
}]
const studentsWithGrade = students.map(student => {
return {
...student,
grade: student.score >= 60 ? '合格' : '不合格'
}
})
4.分组法
const students = [{
group_id: 1,
score: 70,
name: 'xiaoming',
}, {
group_id: 2,
score: 80,
name: 'xiaowang',
}, {
group_id: 1,
score: 50,
name: 'xiaohong',
}]
const studentsWithGroup = students.reduce((groups, student) => {
groups[student.group_id] = [...(groups[student.group_id] || []), student];
return groups;
}, {})
5.联合法
const students = [{
id: 1,
color: 'red',
parent_id: 2,
}, {
id: 2,
color: 'blue',
parent_id: 3,
}, {
id: 3,
color: 'green',
parent_id: 1,
}]
const studentsWithGroup = students.map(e => {
const group = students.find(m => e.parent_id === m.id);
return {
...e,
parent_color: group.color
}
})