请你判断一个9X9的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字1-9在每一行只能出现一次
数字1-9在每一列只能出现一次
数字1-9在每一个以粗实线分割的3X3宫内只能出现一次。
数独部分空格内已填入了数字,空白格用'.'表示
大O表示法
大O表示法全称大O时间复杂度表示法,表示代码执行时间随数据规模增长的变化趋势。大O表示法用于粗略的估计算法的执行效率。所以大O表示法制关注量级最大的那段代码(一般就是循环执行次数最多的那段代码)
-
O(1):代码中不存在循环语句、递归语句,即使千行万行,复杂度也是O(1);
-
O(n):代码中存在循环语句、递归语句如下
const print = n => { for(let i = 0; i < n; i++) { console.log(i) } }该方法中, print 方法中 for 循环执行了 n 次,所以时间复杂度为O(n);
-
O(logn) 代码中存在循环,但是循环条件为:
const print = n => { let i = 1; while(i <= n) { i = i * 2; console.log(i); } }代码中循环条件变量 i 每循环一次就乘以2,当 i 大于 n 时循环条件结束。所以代码循环的次数就是下图中的x
估计大多数人都已忘了高中所学的等比数列,更忘了什么是对数
在对数阶时间复杂度的表示方法里,我们忽略对数的"底",统一表示为 O(logn).
如果 print 方法在循环中执行了 n 次,那么时间复杂度就是 O(nlogn) 。
和有限的最长子序列
给你一个长度为 n 的整数数组 nums ,和一个长度为 m 的整数数组 queries 。
返回一个长度为 m 的数组 answer ,其中 answer[i] 是 nums 中 元素之和小于等于 queries[i] 的 子序列 的 最大 长度 。
子序列 是由一个数组删除某些元素(也可以不删除)但不改变剩余元素顺序得到的一个数组。
// 输入:nums = [4,5,2,1], queries = [3,10,21]
// 输出:[2,3,4]
// 解释:queries 对应的 answer 如下:
// - 子序列 [2,1] 的和小于或等于 3 。可以证明满足题目要求的子序列的最大长度是 2 ,所以 answer[0] = 2 。
// - 子序列 [4,5,1] 的和小于或等于 10 。可以证明满足题目要求的子序列的最大长度是 3 ,所以 answer[1] = 3 。
// - 子序列 [4,5,2,1] 的和小于或等于 21 。可以证明满足题目要求的子序列的最大长度是 4 ,所以 answer[2] = 4
var answerQueries = function(nums, queries) {
const n = nums.length, m = queries.length;
nums.sort((a, b) => a - b);
const f = new Array(n + 1).fill(0);
for(let i = 0; i < n; i++) {
f[i + 1] = f[i] + nums[i];
}
const answer = new Array(m).fill(0);
for(let i = 0; i < m; i++) {
answer[i] = binarySearch(f, queries[i]) - 1;
}
return answer;
}
const binarySearch = (f, target) => {
let low = 1, high = f.length;
while (low < high) {
const mid = low + Math.floor((high - low) / 2);
if (f[mid] > target) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
};
1140. 石子游戏 II
爱丽丝和鲍勃继续他们的石子游戏。许多堆石子排成一行,每堆都有正整数颗石子 piles[i]。游戏以谁手中的石子最多来决出胜负。
爱丽丝和鲍勃轮流进行,爱丽丝先开始。最初,M = 1。
在每个玩家的回合中,该玩家可以拿走剩下的前X堆的所有石子, 其中 1 <= X <= 2M。然后,另 M = max(M, X).
游戏一直持续到所有石子都被拿走。
假设爱丽丝和鲍勃都发挥出最佳水平,返回爱丽丝可以得到的最大数量的石头。
// 输入:piles = [2,7,9,4,4]
// 输出:10
// 解释:如果一开始Alice取了一堆,Bob取了两堆,然后Alice再取两堆。爱丽丝可以得到2 + 4 + 4 = 10堆。如果Alice一开始拿走了两堆,那么Bob可以拿走剩下的三堆。在这种情况下,Alice得到2 + 7 = 9堆。返回10,因为它更大。
const stoneGameII = function(piles) {
const prefixSum = new Array(piles.length + 1).fill(0);
for(let i = 0; i < piles.length; i++) {
prefixSum[i + 1] = prefixSum[i] + piles[i];
}
const memo = new Map();
return Math.floor((prefixSum[piles.length] + dp(memo, piles, prefixSum, 0, 1)) / 2);
}
const dp = (memo, piles, prefixSum, i, m) => {
if(i === piles.length) {
return 0;
}
const key = i * 201 + m;
if(!memo.has(key)) {
let maxVal = -Number.MAX_VALUE;
for(let x = 1; x <= 2 * m; x++) {
if(i + x > piles.length) {
break;
}
maxVal = Math.max(maxVal, prefixSum[i + x] - prefixSum[i] - dp(memo, piles, prefixSum, i + x, Math.max(m, x)));
}
memo.set(key, maxVal);
}
return memo.get(key);
}
强密码检查器
// 如果一个密码满足以下所有条件,我们称它是一个 强 密码:
// 它有至少 8 个字符。
// 至少包含 一个小写英文 字母。
// 至少包含 一个大写英文 字母。
// 至少包含 一个数字 。
// 至少包含 一个特殊字符 。特殊字符为:"!@#$%^&*()-+" 中的一个。
// 它 不 包含 2 个连续相同的字符(比方说 "aab" 不符合该条件,但是 "aba" 符合该条件)。
// 给你一个字符串 password ,如果它是一个 强 密码,返回 true,否则返回 false 。
// 输入:password = "IloveLe3tcode!"
// 输出:true
// 解释:密码满足所有的要求,所以我们返回 true 。
var strongPasswordCheckerII = password => {
return /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()\-+])(?!.*(.)\1+).{8,}$/.test(password)
}
DFS
Depth first search 成为」深度优先遍历「
下面结合具体例子来理解。如图所示,在一个九宫格图中,绿色位置代表起始位置,红色位置代表重点位置,灰色区域和宫格图的边界代表此路不通,请从起始位置按照每次只能移动一格的方法移动到终点位置
我们用DFS的方法去解这道题,由图可知,我们可以走上下左右四个方向,我们不妨先约定"左>上>右>下"的顺序走,即,如果左边可以走,我们先走左边。然后」递归「下去,没达到终点,我们在原路返回,等又返回到这个位置时,左边已经走过了,那么我们就走上面,按照这个顺序走。并且我们把走过的路(方向)作为标记表示"不能走回头路"
前言
背包问题是一类经典的算法问题,属于动态规划解法范畴,其核心是在一个范围内选择出最优解。
一般描述为:给定一组物品和一个背包,每种物品都有自己的重量和价格,在背包限定的总重量内,我们如何选择,才能使得物品的总价格最高
非完全背包
如下面的背包基础问题
-
描述
有若干物品和一个大小为m的背包,给定数组A表示物品的大小和数组V表述每个物品的价值
问最多能装入背包的总价值是多大?
-
样例1
// 输入 m = 10 A = [2,3,5,7] V = [1,5,2,4] // 输出9装入A[1]和A[3]可以得到最大价值,V[1]+V[3] = 9;
-
样例2
// 输入 m = 10 A = [2,3 8] V = [2,5,8] // 输出10装入A[0]和A[2]可以得到最大价值,V[0]+V[2] = 10;
贪心算法
算法策略
贪心算法,顾名思义,总是做出当前的最优选择,即期望通过局部的最优选择获得整体的最优选择。
某种意义上说,贪心算法是很贪婪、很目光短浅的,它不从整体考虑,仅仅只关注当前的最大利益,所以说它做出的选择仅仅是某种意义上的局部最优,但是贪心算法在很多问题上还是能够拿到最优解或较优解,所以它的存在还是有意义的。
在日常生活中,我们使用到贪心算法的时候还是挺多的,例如:从100张面值不等的钞票中,抽出10张,怎么样才能获得最多的价值?
接雨水
let trap = height => {
let stack = [];
let ans = 0;
let n = height.length;
for(let i = 0; i < n; i++) {
if(stack.length && height[i] > height[stack[stack.length - 1]]) {
let top = stack.pop();
if(!stack.length) break;
let left = stack[stack.length - 1];
let curWidth = i - left - 1;
let curHeight = Math.min(height[left], height[i] - height[top]);
ans += curWidth * curHeight;
}
stack.push(i);
}
return ans;
}
Dijkstra算法(迪杰斯特拉算法)是一种计算从单个源到所有其他源的最短路径的贪心算法(无法解决边权值为负的情况)
考虑下图
let graph = [
[0, 2, 4, 0, 0, 0],
[0, 0, 2, 4, 2, 0],
[0, 0, 0, 0, 3, 0],
[0, 0, 0, 0, 0, 2],
[0, 0, 0, 3, 0, 2],
[0, 0, 0, 0, 0, 0]
];
// a-b-c-d-e-f b-a-b-c-d-e-f c-a-b-c-d-e-f ...
let INF = Number.MAX_SAFE_INTEGER // MAX_SAFE_INTEGER属性表示在JavaScript的最大安全整数
// 从尚未处理的顶点选出距离最近的顶点
function minDistance(dist, visited) {
let min = INF;
let minIndex = -1;
for(let i = 0; i < dist.length; i++) {
if(!visited[i] && dist[i] < min) {
minIndex = i;
min = dist[i];
}
}
return minIndex;
}
function dijkstra(src) {
let dist = [];
let visited = [];
let length = graph.length;
for(let i = 0; i < length; i++) { // (1)
dist[i] = INF;
visited[i] = false;
}
dist[src] = 0; // (2);
for(let j = 0; j < length - 1; j++) { // (3)
let u = minDistance(dist, visited); // (4)
visited[u] = true; // (5)
for(let v = 0; v < length; v++) {
if(
!visited[v] && // (6)
dist[u] != INF && // (7)
graph[u][v] != 0 && // (8)
dist[u] + graph[u][v] < dist[v] // (9)
) {
dist[v] = dist[u] + graph[u][v] // (10)
}
}
}
return dist // (11)
}
const res = dijkstra(0);
console.log(res, 'res'); // [0, 2,4, 5, 4,6];
// 求B点到其他点的最短距离
// const res = dijkstra(1);
// Infinity表示点B与点A不相邻,从上图可知A与B相邻,而B与A不相邻
// [ Infinity, 0, 2, 4, 2, 4 ]