From fabf3a1b7bf84816d4f250d68c2e61e37c92d531 Mon Sep 17 00:00:00 2001 From: gameloader Date: Fri, 6 Sep 2024 11:34:54 +0800 Subject: [PATCH] leetcode update --- content/posts/leetcode.md | 1182 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1182 insertions(+) diff --git a/content/posts/leetcode.md b/content/posts/leetcode.md index cf10718..5a10e05 100644 --- a/content/posts/leetcode.md +++ b/content/posts/leetcode.md @@ -11396,3 +11396,1185 @@ func abs(input int)int{ return input } ``` + +## day173 2024-08-17 +### 1937. Maximum Number of Points with Cost +You are given an m x n integer matrix points (0-indexed). Starting with 0 points, you want to maximize the number of points you can get from the matrix. + +To gain points, you must pick one cell in each row. Picking the cell at coordinates (r, c) will add points[r][c] to your score. + +However, you will lose points if you pick a cell too far from the cell that you picked in the previous row. For every two adjacent rows r and r + 1 (where 0 <= r < m - 1), picking cells at coordinates (r, c1) and (r + 1, c2) will subtract abs(c1 - c2) from your score. + +Return the maximum number of points you can achieve. + +abs(x) is defined as: + +x for x >= 0. +-x for x < 0. + +![0817XUKrbszCuxge](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0817XUKrbszCuxge.png) + +### 题解 +本题每一行中各个位置能得到的最大点数仅与上一行有关, 因此只要解决两行矩阵第二行如何根据第一行求得每个位置能得到的最大点数这一子问题即可得解. 最直接的想法当然是暴力, 对第二行每个数字都遍历第一行的全部数字, 每个数字都通过点数-距离得到其对于第二行该数字的价值. 最后取价值最大的数加在该数字上, 这种方式如果每行数字个数为n个, 需要n^2的复杂度. 则若共有m行, 整体需要m*n^2的复杂度. 因此我们需要思考一些方式使得每行只需遍历常数次即能让下一行每个位置找到上一行中的最大值. 这个问题的思路在某些地方与"接雨水"这到经典题有相似之处. 虽然我们在从左向右遍历的时候对于任意位置不能得到全局最大值, 但在遍历的过程中其实已经获得了当前下标左侧的全部值的信息, 因此我们可以记录到某一下标的左侧数组中的全部数字到这一下标的最大价值, 只需定义一个变量leftmax, 并在遍历过程中不断将leftmax-1和当前下标的数字比较并更新leftmax. 举例来说, 如1,3,2,6,4这几个数字, 遍历1时leftmax为1, 遍历3时将leftmax-1(1-1)和3比较得到新的leftmax为3, 遍历2时将leftmax-1(3-1)和2比较得到新的leftmax为2. 将每个位置的leftmax都保存下来, 这样相当于将求每个位置的最大价值这一问题拆为两个子问题, 即求解每个位置左侧的最大价值和每个位置右侧的最大价值. 最后再将这两个问题的解比较即得最终解. 这样的好处在于这两个子问题都是可以"并行求解"的, 即对行r遍历一遍行r-1即可求出所有位置的一个子问题的解, 求得两个子问题的解也只需两次. 而原问题则需要每个位置都遍历一遍行r-1, 在遍历的过程中虽然访问了行r-1的全部数字, 但大部分信息都被抛弃了, 在下一个位置又要重新获取这些信息. 这是非常低效的, 在之前题解中多次强调, 能越充分的利用信息就有越高的求解效率. + +因此本题求解过程为对行r, 遍历两次行r-1获得全部位置的leftmax和rightmax, 最后遍历一遍行r并更新每个位置的最大价值即可. + +### 代码 +```go +func maxPoints(points [][]int) int64 { + rows := len(points) + if rows == 1{ + return int64(maxSlice(points[0])) + } + cols := len(points[0]) + leftmax := make([]int, cols) + rightmax := make([]int, cols) + for row:=1;row=0;index--{ + rightmax[index] = max(rightmaxnow-1, points[row-1][index]) + rightmaxnow = rightmax[index] + } + for col:=0;col maxNum { + maxNum = num + } + } + return maxNum +} + +``` + +## day173 2024-08-18 +### 264. Ugly Number II +An ugly number is a positive integer whose prime factors are limited to 2, 3, and 5. + +Given an integer n, return the nth ugly number. + +![0818t4Rgq13Am2NL](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0818t4Rgq13Am2NL.png) + +### 题解 +本题通过暴力遍历每个数并判断是不是丑数显然比较低效, 最好的办法是直接生成丑数, 问题在于如何生成丑数呢, 回想类似的题目可以直接通过之前的具有相似性质的数字生成后面的数字, 那么丑数也可能具有这种特点, 可以通过数学归纳法证明. +a) 设第n+1个丑数为U。根据丑数的定义,U可以表示为: + +U = 2^a * 3^b * 5^c,其中a、b、c都是非负整数。 + +b) 我们可以将U表示为以下三种形式之一: + +U = 2 * (2^(a-1) * 3^b * 5^c) 当a > 0时 +U = 3 * (2^a * 3^(b-1) * 5^c) 当b > 0时 +U = 5 * (2^a * 3^b * 5^(c-1)) 当c > 0时 +c) 注意到括号中的表达式(2^(a-1) * 3^b * 5^c)、(2^a * 3^(b-1) * 5^c)和(2^a * 3^b * 5^(c-1))都是更小的丑数,因为它们的指数和比U小。 + +d) 根据归纳假设,这些更小的丑数已经在我们生成的n个丑数中。 + +e) 因此,U必定可以通过将某个已知的丑数乘以2、3或5得到。 + +则由此, 生成丑数可以通过之前的丑数分别乘以2,3,5得到, 为了保证得到的丑数是有序的, 则可以用三个指针分别表示2,3,5三个数当前已经乘过的丑数是哪个. 每次取三个指针位置的数乘以对应的被乘数中最小的.如初始丑数都为1, 设指针分别为i2,i3,i5, 则先将2乘1得到2, 同时指针i2指向1表示下标为0的数字(1)已经乘过2了, 继续这个操作不断计数, 直到得到目标丑数. + +### 代码 +```go +func nthUglyNumber(n int) int { + ugly := make([]int, n) + ugly[0] = 1 + + i2, i3, i5 := 0, 0, 0 + + for i := 1; i < n; i++ { + next2 := ugly[i2] * 2 + next3 := ugly[i3] * 3 + next5 := ugly[i5] * 5 + + nextUgly := min(next2, min(next3, next5)) + ugly[i] = nextUgly + + if nextUgly == next2 { + i2++ + } + if nextUgly == next3 { + i3++ + } + if nextUgly == next5 { + i5++ + } + } + + return ugly[n-1] +} +``` + +## day174 2024-08-19 +### 650. 2 Keys Keyboard +There is only one character 'A' on the screen of a notepad. You can perform one of two operations on this notepad for each step: + +Copy All: You can copy all the characters present on the screen (a partial copy is not allowed). +Paste: You can paste the characters which are copied last time. +Given an integer n, return the minimum number of operations to get the character 'A' exactly n times on the screen. + +![0819quqWn30l1biR](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0819quqWn30l1biR.png) + +### 题解 +本题题目有点幽默, 一个经典的程序员梗, 程序员键盘上只需要两个键就够了, 一个复制, 一个粘贴. 本题正是一个这样的键盘. + +回归题目本身, 第一步可以发现如果n能被一个正整数a整除, 则可以通过将n/a个数的'A'粘贴a-1次得到n个'A', 加上复制操作总共需要的操作次数为a次. 则可以发现只要将n分解成几个因子相乘, 将这几个因子相加就是一个得到n的操作总数. 接下来的问题就是如何分解能让这个操作总数最小, 这里很容易想到将n分解为质因数, 因为质因数是比较特殊的因数, 这是我们的猜测, 可以通过简单的证明说明分解成质因数一定不大于分解成合数因数的操作次数, 只需证明如果a,b均大于1则a+b(质因数的操作总数)<=a*b(合数因数的操作总数). + +首先,我们可以考虑 ab - (a+b),如果能证明这个差值非负,那么就证明了 ab ≥ a+b。 + +a*b - (a+b) = ab - a - b += ab - a - b + 1 - 1 += (a-1)(b-1) - 1 + +因为a和b都是大于1的正整数,所以 a-1 ≥ 1 且 b-1 ≥ 1 + +因此,(a-1)(b-1) ≥ 1 + +所以,(a-1)(b-1) - 1 ≥ 0 + +这就证明了 ab - (a+b) ≥ 0,即 ab ≥ a+b + +由此我们得到只要将n分解成质因数相乘再将这些因子相加就能得到最少的操作总数. 如何将n分解成质因数相乘呢, 只需从2开始不断用n试除质因子, 当无法整除时将质因子加一(不用担心可以被合数整除, 如如果一个数能被6整除, 则从2递增到3时一定已经将这个数除尽了), 直到因子大于等于n的平方根即可. 每次分解出一个质因子都将其加和到操作总数上即得最终的答案. + +### 代码 +```go +func minSteps(n int) int { + if n == 1{ + return 0 + } + d := 2 + result := 0 + for n > 1 { + for n%d == 0 { + result += d + n /= d + } + d++ + if d*d > n { + if n > 1 { + result += n + } + break + } + } + return result + +} +``` + +## day 175 2024-08-20 +### 1140. Stone Game II +Alice and Bob continue their games with piles of stones. There are a number of piles arranged in a row, and each pile has a positive integer number of stones piles[i]. The objective of the game is to end with the most stones. + +Alice and Bob take turns, with Alice starting first. Initially, M = 1. + +On each player's turn, that player can take all the stones in the first X remaining piles, where 1 <= X <= 2M. Then, we set M = max(M, X). + +The game continues until all the stones have been taken. + +Assuming Alice and Bob play optimally, return the maximum number of stones Alice can get. + +![0820rbyn5xa0NtDv](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0820rbyn5xa0NtDv.png) + +### 题解 +本题思考对于Alice取石子的场景, 如何知道Alice最终最多能取到多少石子, 对于最简单的场景, 如当前Alice只能取一堆或者两堆石子, 我们只需知道当Alice取一堆石子后后续最多能取到多少石子, 并与当前一堆石子加和, 即可知道如果选择只取一堆石子Allice最终最多能得到多少石子, 取两堆石子同理. 则问题变为当Alice已经取完一次石子后, 后续Alice最多能取到多少石子, 对于Alice来说, 目标为取得最多的石子, 而对于Bob来说, 目标则是让Alice取到最少的石子. 则可以设定一个flag表示当前是Alice的回合还是Bob的回合, 如果是Alice的回合, 则尝试所有可行的取石子方法并加上取石子后剩余的石子堆中Alice能取到的最大石子数再取一个最大值. 如果是Bob的回合, 则也尝试所有可行的取石子方法并返回Alice能取石子的最小值. 如石子总共有3堆, Alice先取可以取一堆或者两堆, 则Alice取一堆加上剩余两堆Alice还能取得的最大值. 剩余两堆中Bob同样可以取一堆或者取两堆, 如果Bob选择取一堆则Alice还能取到第三堆, 如果Bob选择取两堆, 则Alice只能取到0, 对Bob来说, 当然是最小化Alice能取的石子个数即让Alice取0, 此时剩余两堆中Alice能取得的最大值就是0. 用状态来表示可以表示为取0加上A(2,1,0). 其中2表示下一个人从第2堆开始取, 1表示上一个人取完后M的值, 即下一个人可以取的堆上限的1/2, 0表示下一个取的人该是Bob, 1表示该是Alice. 将状态记忆化, 遇上重复的状态不需要再次递归计算. + +这类问题的关键是把大问题抽象成一个统一的结构, 分解成小问题解决, 在本题中即为当Alice可取的堆数从1到2M时, 只需考虑用Alice取的堆数加上剩余的堆中Alice能取得的最大值即可. 至于剩余的堆中Alice能取得最大值怎么计算, 因为此问题的结构和前一个问题完全相同, 则我们一定可以通过不断递归将问题规模不断变小最终到达一个终止状态得到解决, 因此不用过于考虑计算细节, 只需确定好终止状态, 其余部分通过结构相同问题递归处理就能得到最终答案. + +### 代码 +```go +func stoneGameII(piles []int) int { + memo := make(map[string]int) + length := len(piles) + var dfs func(i, M int, flag bool) int + dfs = func(i, M int, flag bool) int{ + if i == length { + return 0 + } + + key := fmt.Sprintf("%d,%d,%v", i, M, flag) + if val, exists := memo[key]; exists { + return val + } + + cnt := 0 + var mx int + if flag { + mx = 0 + } else { + mx = math.MaxInt32 + } + + for j := i; j < min(length, i+2*M); j++ { + cnt += piles[j] + val := dfs(j+1, max(M, j-i+1), !flag) + if flag { + val += cnt + mx = max(mx, val) + } else { + mx = min(mx, val) + } + } + memo[key] = mx + return mx + } + return dfs(0, 1, true) +} + +``` + +## day 176 2024-08-21 +### 664. Strange Printer +There is a strange printer with the following two special properties: + +The printer can only print a sequence of the same character each time. +At each turn, the printer can print new characters starting from and ending at any place and will cover the original existing characters. +Given a string s, return the minimum number of turns the printer needed to print it. + +![08213P3Mjb62YhEb](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/08213P3Mjb62YhEb.png) + +### 题解 +本题是一道难题, 难在如何把问题转换为相同结构的子问题, 本题打印字符串过程的区间是非常重要的, 根据题目可以发现, 如果字符串两端是相同的, 如abba, 则在打印一串a的时候可以同时打印出两端的两个a. 则abba需要打印的次数和abb需要打印的次数相同, abb打印的次数由abb的两个子问题决定, 分别是ab和b,a和bb两个问题的打印次数的最小值决定. 由此可知, 本题可将一个区间内打印次数的问题转换为区间内子区间的打印次数的问题, 区间两端值相同时直接等于子区间的次数, 不同则需遍历所有可能取最小值. 由此不断减小问题规模直到区间长度为1, 直接返回1次即可. 如昨天所言, 只需考虑问题如何由子问题组合得到答案, 不需考虑子问题具体如何求解, 只需将子问题整个当作可以求解并求解好的结果直接使用. 子问题再通过递归自行求解. + +### 代码 +```go + +func strangePrinter(s string) (result int) { + newstr := string(s[0]) + + for i:= 1 ;i < len(s); i++ { + if s[i] != s[i-1] { + newstr += string(s[i]) + } + } + + m := len(newstr) + + dp := make([][]int,m) + for i:=range dp{ + dp[i] = make([]int,m) + } + + for i:=0;i=0;j--{ + if i == j { + dp[i][j] = 1 + continue + }else{ + dp[j][i] = math.MaxInt32 + } + if newstr[i] == newstr[j] { + dp[j][i] = dp[j][i-1] + }else{ + for k:=j;k 0{ + bit = num % 2 + num = num >> 1 + bit = bit ^ 1 + result += bit << count + count++ + } + return result +} +``` + +## day178 2024-08-23 +### 592. Fraction Addition and Subtraction +Given a string expression representing an expression of fraction addition and subtraction, return the calculation result in string format. + +The final result should be an irreducible fraction. If your final result is an integer, change it to the format of a fraction that has a denominator 1. So in this case, 2 should be converted to 2/1. + +![0823PZUrYHXGbUme](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0823PZUrYHXGbUme.png) + +### 题解 +本题关键在于寻找公倍数和最大公约数, 因为题目中分母的大小都在1到10之间, 所以只需找到一个公倍数(不要求最小)将两个分数的分母统一起来方便做加减法. 而需要最大公约数则是因为运算得到的分数要化成最简式. 找最大公约数有一个经典的欧几里得算法(辗转相除法). 题目思路为遇到运算符即处理运算符随后处理运算符后的分数. 首个分数即时是负的也可以通过0减去该分数的方式与后面的处理统一起来. 为了简化计算直接将所有分母都转换成1-10的公倍数2520. 这样只处理分子即可. + +### 代码 +```go +func fractionAddition(expression string) string { + if expression[0] != '-' { + expression = "+" + expression + } + sign := 1 + i, n := 0, len(expression) + nume := 0 + nume1, deno1 := 0, 0 + LCM := 2520 + + for i < n { + if expression[i] == '+' { + sign = 1 + } else { + sign = -1 + } + i++ + + // 处理分子 + nume1 = int(expression[i] - '0') + i++ + if i < n && expression[i] != '/' { + nume1 = nume1*10 + int(expression[i]-'0') + i++ + } + + // 处理分母 + i++ // 跳过 '/' + deno1 = int(expression[i] - '0') + i++ + if i < n && expression[i] != '+' && expression[i] != '-' { + deno1 = deno1*10 + int(expression[i]-'0') + i++ + } + + nume1 = LCM / deno1 * sign * nume1 + nume += nume1 + } + + var result strings.Builder + if nume == 0 { + return "0/1" + } else if nume < 0 { + nume = -nume + result.WriteString("-") + } + GCD := getGCD(nume, LCM) + result.WriteString(strconv.Itoa(nume / GCD)) + result.WriteString("/") + result.WriteString(strconv.Itoa(LCM / GCD)) + return result.String() +} + +// 求最大公约数(欧几里得算法) +func getGCD(a, b int) int { + for b != 0 { + a, b = b, a%b + } + return a +} +``` + +## day179 2024-08-24 +### 564. Find the Closest Palindrome +Given a string n representing an integer, return the closest integer (not including itself), which is a palindrome. If there is a tie, return the smaller one. + +The closest is defined as the absolute difference minimized between two integers. + +![0824i7QPcWT0L30M](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0824i7QPcWT0L30M.png) + +### 题解 +本题考虑能得到离一个数最近的回文数的所有情况, 假设数字根据长度中间位置有一个"对称轴". 则在对称轴两侧的数字完全按逆序排列即得到回文数, 从原始数字得到回文数可以直接复制左半边的数字并逆序, 可以将左半边数字加一并逆序, 也可以将左半边数字减一并逆序. 只需要比较这几种情况得到的回文数哪个和原数字的差最小即可. 但除此以外还要考虑如999这样的数字, 距离其最近的回文数为1001, 同样对于1000来说, 其对应的回文数为999, 因此需要将这两种情况也考虑在内. 因此对于任意数字, 列举这五种情况下得到的回文数并选择与原数字差最小的即可. 注意对于本身已经是回文数的不能选择自身, 即差必须大于0. + +### 代码 +```go +func nearestPalindromic(n string) string { + num, _ := strconv.Atoi(n) + + length := len(n) + candidates := []int{} + + // 情况1:999 -> 1001 + candidates = append(candidates, int(math.Pow10(length)) + 1) + + // 情况2:1000 -> 999 + candidates = append(candidates, int(math.Pow10(length-1)) - 1) + + // 获取左半部分 + leftHalf := n[:(length+1)/2] + leftNum, _ := strconv.Atoi(leftHalf) + + // 情况3:直接镜像 + candidates = append(candidates, createPalindrome(leftNum, length%2 == 0)) + + // 情况4:左半部分+1 + candidates = append(candidates, createPalindrome(leftNum+1, length%2 == 0)) + + // 情况5:左半部分-1 + candidates = append(candidates, createPalindrome(leftNum-1, length%2 == 0)) + + closest := candidates[0] + minDiff := abs(num - closest) + + + for _, candidate := range candidates[1:] { + diff := abs(num - candidate) + if (diff < minDiff && diff > 0) || (diff == minDiff && candidate < closest) { + closest = candidate + minDiff = diff + } + } + + return strconv.Itoa(closest) +} + +func createPalindrome(num int, even bool) int { + palindrome := num + if !even { + num /= 10 + } + for num > 0 { + palindrome = palindrome*10 + num%10 + num /= 10 + } + return palindrome +} + +func abs(x int) int { + if x < 0 { + return -x + } + return x +} + +``` + +## day180 2024-08-25 +### 145. Binary Tree Postorder Traversal +Given the root of a binary tree, return the postorder traversal of its nodes' values. + +![0825UT0EkQ59RcnE](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0825UT0EkQ59RcnE.png) + +### 题解 +本题为基础题, 考查最简单的二叉树遍历方法, 使用递归遍历二叉树, 先遍历左子节点, 再遍历右子节点, 最后将根节点的值加入到结果数组当中. + +### 代码 +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func postorderTraversal(root *TreeNode) []int { + if root == nil{ + return []int{} + } + result := []int{} + var traversal func(*TreeNode) + traversal = func(fa *TreeNode){ + if fa.Left != nil{ + traversal(fa.Left) + } + if fa.Right != nil{ + traversal(fa.Right) + } + result = append(result, fa.Val) + } + traversal(root) + return result +} +``` +## day181 2024-08-26 +### 590. N-ary Tree Postorder Traversal +Given the root of an n-ary tree, return the postorder traversal of its nodes' values. + +Nary-Tree input serialization is represented in their level order traversal. Each group of children is separated by the null value (See examples) + +![0826V88pNkYMVEH1](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0826V88pNkYMVEH1.png) + +### 题解 +本题和昨天的二叉树遍历思路上一致, 只需要将二叉树遍历的左右子节点改为遍历父节点的孩子节点数组即可. + +### 代码 +```go +/** + * Definition for a Node. + * type Node struct { + * Val int + * Children []*Node + * } + */ + +func postorder(root *Node) []int { + if root == nil{ + return []int{} + } + result := []int{} + var traversal func(*Node) + traversal = func(fa *Node){ + if len(fa.Children) > 0{ + for _, child := range fa.Children{ + traversal(child) + } + } + result = append(result, fa.Val) + } + traversal(root) + return result +} +``` + +## day182 2024-08-27 +### 1514. Path with Maximum Probability +You are given an undirected weighted graph of n nodes (0-indexed), represented by an edge list where edges[i] = [a, b] is an undirected edge connecting the nodes a and b with a probability of success of traversing that edge succProb[i]. + +Given two nodes start and end, find the path with the maximum probability of success to go from start to end and return its success probability. + +If there is no path from start to end, return 0. Your answer will be accepted if it differs from the correct answer by at most 1e-5. + +![0827fiB1rHFThy9y](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0827fiB1rHFThy9y.png) + +### 题解 +本题寻找从一个点到另外一个点的最大可能性, 其实与寻找从一个点到另外一个点的最小距离异曲同工, 而这个图是一个无向图且边的权重均为正, 这能让我们想到dijistra算法. 只是需要对dijistra算法进行一些改动, 将计算两点之间的最短距离改为计算两点之间路径的总的可能性, 将每次从队列中取出距离原点距离最近的点改为取出距离原点可能性最大的点, 其余部分不变. + +实现dijistra算法在寻找当前队列中到原点距离最短的节点时,如果使用数组则需遍历整个数组, 但使用优先级队列则可以直接得到有最短距离的节点(本题中为最大可能性) + +### 代码 +```go +type Edge struct { + to int + probability float64 +} + +type Graph [][]Edge + +func buildGraph(n int, edges [][]int, succProb []float64) Graph { + graph := make(Graph, n) + for i, edge := range edges { + from, to := edge[0], edge[1] + prob := succProb[i] + graph[from] = append(graph[from], Edge{to: to, probability: prob}) + graph[to] = append(graph[to], Edge{to: from, probability: prob}) + } + return graph +} + +type Item struct { + node int + probability float64 + index int +} + +type PriorityQueue []*Item + +func (pq PriorityQueue) Len() int { return len(pq) } +func (pq PriorityQueue) Less(i, j int) bool { return pq[i].probability > pq[j].probability } +func (pq PriorityQueue) Swap(i, j int) { + pq[i], pq[j] = pq[j], pq[i] + pq[i].index = i + pq[j].index = j +} + +func (pq *PriorityQueue) Push(x interface{}) { + n := len(*pq) + item := x.(*Item) + item.index = n + *pq = append(*pq, item) +} + +func (pq *PriorityQueue) Pop() interface{} { + old := *pq + n := len(old) + item := old[n-1] + old[n-1] = nil + item.index = -1 + *pq = old[0 : n-1] + return item +} + +func maxProbability(n int, edges [][]int, succProb []float64, start_node int, end_node int) float64 { + graph := buildGraph(n, edges, succProb) + + probs := make([]float64, n) + probs[start_node] = 1.0 + + pq := make(PriorityQueue, 0) + heap.Init(&pq) + heap.Push(&pq, &Item{node: start_node, probability: 1.0}) + + for pq.Len() > 0 { + item := heap.Pop(&pq).(*Item) + node := item.node + prob := item.probability + + if node == end_node { + return prob + } + + if prob < probs[node] { + continue + } + + for _, edge := range graph[node] { + newProb := prob * edge.probability + if newProb > probs[edge.to] { + probs[edge.to] = newProb + heap.Push(&pq, &Item{node: edge.to, probability: newProb}) + } + } + } + + return 0.0 +} +``` + +## day183 2024-08-28 +### 1905. Count Sub Islands +You are given two m x n binary matrices grid1 and grid2 containing only 0's (representing water) and 1's (representing land). An island is a group of 1's connected 4-directionally (horizontal or vertical). Any cells outside of the grid are considered water cells. + +An island in grid2 is considered a sub-island if there is an island in grid1 that contains all the cells that make up this island in grid2. + +Return the number of islands in grid2 that are considered sub-islands. + +![0828Lc8ouMrz7EDc](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0828Lc8ouMrz7EDc.png) + +### 题解 +本题在数grid2岛屿个数的基础上增加判断岛屿中所有陆地块是否都包含在grid1中即可, 如果有任意的陆地块不包含在grid1的陆地中则这个岛屿不算sub island. 判断岛屿中的陆地个数则已经之前多次处理过, 使用dfs即可, 每次都向四个方向进行dfs, dfs过程中判断当前陆地块是否包含在grid1的陆地块中, 不包含则直接返回, 包含继续dfs直到四周均无法继续dfs为止. + +需要注意的是, 在dfs的过程中不能因为grid2中的岛屿中某块陆地已经不在grid1中就直接返回, 因为需要将整块岛屿都遍历完, 如果不将整块岛屿都遍历完并置0的话会出现一块岛屿被分割遍历多次的情况. 在返回的过程中, 如果用true和false表示遍历的岛屿中的陆地在不在grid1中, 则需要设置一个变量并和各个方向dfs的返回结果做并, 而不是直接返回四个方向dfs的并(如dfs1&&dfs2&&dfs3&&dfs4), 因为并运算的短路效应, 如果前面的dfs有为false的情况则后面的dfs不会执行, 而我们需要四个dfs都被执行. 故需要设置单独的变量来保存四个dfs的结果. + +### 代码 +```go +func countSubIslands(grid1 [][]int, grid2 [][]int) int { + rows := len(grid2) + cols := len(grid2[0]) + var dfs func([][]int, [][]int,int,int)bool + dfs = func(g1 [][]int, g2[][]int, row int, col int)bool{ + if row < 0 || row > rows-1 || col < 0 || col > cols-1 || g2[row][col]==0{ + return true + } + + cond := true + if g1[row][col] == 0{ + cond = false + } + g2[row][col] = 0 + cond = dfs(g1, g2, row-1, col) && cond + cond = dfs(g1, g2, row+1, col) && cond + cond = dfs(g1, g2, row, col-1) && cond + cond = dfs(g1, g2, row, col+1) && cond + return cond + } + + results := 0 + for i, rowcontent := range grid2{ + for j, _ := range rowcontent{ + if grid2[i][j] == 1{ + if dfs(grid1, grid2, i, j){ + results++ + } + } + } + } + return results +} +``` + +## day184 2024-08-29 +### 947. Most Stones Removed with Same Row or Column +On a 2D plane, we place n stones at some integer coordinate points. Each coordinate point may have at most one stone. + +A stone can be removed if it shares either the same row or the same column as another stone that has not been removed. + +Given an array stones of length n where stones[i] = [xi, yi] represents the location of the ith stone, return the largest possible number of stones that can be removed. + +![0829acaVmeQ5MUz7](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0829acaVmeQ5MUz7.png) + +### 题解 +本题通过观察可以发现只要彼此之间有关联(共行或共列)的一系列坐标, 最后仅保留一个, 其余均可通过某种顺序被全部删除. 如(0,0), (0,2), (2,0), (2,2), 四个点之前每个都和另外一个有关联, 则最终四个点仅保留一个. 也就意味着, 如果a与b有关联, 则a b可视为在同一个集合中, 若c和a有关联, 则c b a可视为在同一个集合中, 以此类推, 直到没有新的数字加入这个集合, 这里可以使用并查集, 最后只需用全部坐标点的个数减掉集合的个数即得到可以被删除点的最多个数. + +问题在于, 如何构造并查集, 由题目可知, 处在同一行或者同一列的点均可以视为在同一个集合中, 则可以将同一行的点进行union合并,同一列的也合并, 但点是二维的, 而行或列单独是一维的, 将点按照行列合并并不方便, 因此需要想办法将维度降低, 考虑行列的个数最多均为10^4, 则最简单的方法就是将列数加上10^4+1, 从而将其和行数放在同一个轴上, 这样就将每个点的二维行列转换成了一维的行来表示, 列放在全部行的后面来表示. 则基于此对每个点的列转化和和行进行合并操作即可构造需要的并查集, 最后用点的个数减去并查集的集合个数即得需要删除的点数. + +这里很有意思的一个思路在于维度压缩, 因为题目中点的坐标有范围, 则x,y两个轴不会被完全使用, 仅仅使用了x,y两个轴上的一个片段, 则可以将y轴的这个片段投射到x轴的某个原本片段之外的区域上, 这样就将原本在两个轴上的问题转换成了一个轴上的问题, 方便解决. +### 代码 +```go +func removeStones(stones [][]int) int { + fa := make([]int,20005) + for i := range fa{ + fa[i] = i + } + + var find func(int) int + find = func(i int) int { + if i!=fa[i]{ + fa[i] = find(fa[i]) + } + return fa[i] + } + + for _,stone := range stones{ + u := find(stone[0]) + v := find(stone[1]+10001) + fa[u] = v + } + + mp := make(map[int]int) + + for _,stone := range stones{ + mp[find(stone[0])]++ + } + return len(stones)-len(mp) +} +``` + +## day185 2024-08-30 +### 2699. Modify Graph Edge Weights +You are given an undirected weighted connected graph containing n nodes labeled from 0 to n - 1, and an integer array edges where edges[i] = [ai, bi, wi] indicates that there is an edge between nodes ai and bi with weight wi. + +Some edges have a weight of -1 (wi = -1), while others have a positive weight (wi > 0). + +Your task is to modify all edges with a weight of -1 by assigning them positive integer values in the range [1, 2 * 109] so that the shortest distance between the nodes source and destination becomes equal to an integer target. If there are multiple modifications that make the shortest distance between source and destination equal to target, any of them will be considered correct. + +Return an array containing all edges (even unmodified ones) in any order if it is possible to make the shortest distance from source to destination equal to target, or an empty array if it's impossible. + +Note: You are not allowed to modify the weights of edges with initial positive weights. + +![08302Qfrs1K0b16t](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/08302Qfrs1K0b16t.png) + +### 题解 +本题首先注意到目标是要求最短路, 而本题中边权重除了-1外均为正值, 而负值也只有-1一个. 则可以先忽略所有边权重为-1的边, 对其他边可以使用dijistra算法(使用优先级队列优化算法效率). 得到的结果有四种情况, 比target大, 比target小, 和target相等, 以及从源点到目标点之间没有可行路径. 若和target相等, 则将其余-1边均置为一个任意大的数, 不影响最终结果即可. 考虑到target最大为10^9, 可以置为10^9+1. 若比target小, 则不可能通过修改-1边的值得到一条最短路径和target相等(因为当前最短路径已经比target小, 若得到新的最短路只能比当前最短路更小, 只会距离target越来越远), 这种情况返回空集即可. 若比target大或没有可行路径, 则需考虑如何处理-1边. 修改-1边相当于将之前忽略的边加入到图中, 加入到图后要考虑两个问题, 加入忽略的边后是否能得到一条新的从源点到目标点的路径, 以及这条路径的权重和与target的大小关系. 在处理-1边时需要先解决第一个问题, 能否得到新的路径, -1权重被修改后的范围为1-2*10^9, 因此我们将边-1的权重先修改为1, 再执行dijistra算法, 看是否能得到从源点到目标点的小于等于target的路径. 不能则证明这条-1边权重修改对目标没有影响, 继续修改下一条-1边权重为1, 再次执行dijistra算法直到dijistra算法的结果小于等于target为止. 此时若小于target则将此时被修改权重的-1边权重调整一下, 增加target和当前路径权重和的差值即得到了等于target的路径. 剩余的-1边一样赋予一个足够大的不影响结果的值即可. + +注意此处需要每次修改一条-1边的权重就执行一次dijistra, 因为每一条边能产生的影响都是独立的, 无法预知任意一条-1边对结果的影响, 修改-1的权重相当于给图增加一条新的边, 单独处理每条边的影响方便我们调整这条边的权重得到路径和为target的路径, 如果同时增加两条边, 则还需要判断应该调整哪条边才能得到目标权重, 这一过程进一步增加了复杂性. + + +### 代码 +```go +var INF int + +type Edge struct { + to, weight int +} + +type PQItem struct { + node, dist int +} + +type PriorityQueue []*PQItem + +func (pq PriorityQueue) Len() int { return len(pq) } +func (pq PriorityQueue) Less(i, j int) bool { return pq[i].dist < pq[j].dist } +func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } + +func (pq *PriorityQueue) Push(x interface{}) { + item := x.(*PQItem) + *pq = append(*pq, item) +} + +func (pq *PriorityQueue) Pop() interface{} { + old := *pq + n := len(old) + item := old[n-1] + *pq = old[0 : n-1] + return item +} + +func runDijkstra(graph [][]Edge, source, destination int) int { + n := len(graph) + minDistance := make([]int, n) + for i := range minDistance { + minDistance[i] = INF + } + minDistance[source] = 0 + + pq := make(PriorityQueue, 0) + heap.Init(&pq) + heap.Push(&pq, &PQItem{node: source, dist: 0}) + + for pq.Len() > 0 { + current := heap.Pop(&pq).(*PQItem) + if current.node == destination { + return current.dist + } + if current.dist > minDistance[current.node] { + continue + } + for _, edge := range graph[current.node] { + newDist := current.dist + edge.weight + if newDist < minDistance[edge.to] { + minDistance[edge.to] = newDist + heap.Push(&pq, &PQItem{node: edge.to, dist: newDist}) + } + } + } + return INF +} + +func modifiedGraphEdges(n int, edges [][]int, source int, destination int, target int) [][]int { + INF = target+1 + graph := make([][]Edge, n) + for i := range edges { + if edges[i][2] != -1 { + graph[edges[i][0]] = append(graph[edges[i][0]], Edge{to: edges[i][1], weight: edges[i][2]}) + graph[edges[i][1]] = append(graph[edges[i][1]], Edge{to: edges[i][0], weight: edges[i][2]}) + } + } + + currentShortestDistance := runDijkstra(graph, source, destination) + if currentShortestDistance < target { + return [][]int{} + } + + matchesTarget := currentShortestDistance == target + + for i := range edges { + if edges[i][2] == -1 { + if matchesTarget { + edges[i][2] = INF + } else { + edges[i][2] = 1 + } + graph[edges[i][0]] = append(graph[edges[i][0]], Edge{to: edges[i][1], weight: edges[i][2]}) + graph[edges[i][1]] = append(graph[edges[i][1]], Edge{to: edges[i][0], weight: edges[i][2]}) + + if !matchesTarget { + newDistance := runDijkstra(graph, source, destination) + if newDistance <= target { + edges[i][2] += target - newDistance + graph[edges[i][0]][len(graph[edges[i][0]])-1].weight = edges[i][2] + graph[edges[i][1]][len(graph[edges[i][1]])-1].weight = edges[i][2] + matchesTarget = true + } + } + } + } + + if matchesTarget { + return edges + } + return [][]int{} +} +``` + +## day186 2024-08-31 +### 1. Two Sum +Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target. + +You may assume that each input would have exactly one solution, and you may not use the same element twice. + +You can return the answer in any order. + +![0831COEbBIu5rdYe](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0831COEbBIu5rdYe.png) + +### 题解 +leetcode每日一题系统貌似出问题了(英文版leetcode), 今天的每日一题重复了前几天的1514题, 看了看那天的题目不知什么时候被换成了这道两数和问题, 作为leetcode第一题其地位不亚于abandon. 本题是一道简单题, 只需先扫描一遍数组将数字作为key将其在原数组中的下标作为value存放在一个哈希表中. 再扫描数组, 查询target和当前数字的差值是否存在于哈希表中, 如果存在则返回当前数字和差值作为key的value组成的数组, 注意处理一些特殊情况, 如当差值和数字自身相同时, 因为数组中可能出现重复数字, 如果有多个重复数字, 则返回这个数字的两个不同下标, 如果不重复, 因为不能返回两个相同的下标作为结果, 则跳过这个数字继续向下处理. + +### 代码 +```go +func twoSum(nums []int, target int) []int { + find := map[int][]int{} + for i,num := range nums{ + find[num] = append(find[num], i) + } + for _,num := range nums{ + if len(find[target-num])>0{ + if num != target-num{ + return []int{find[num][0], find[target-num][0]} + }else if num == target-num && len(find[num]) > 1{ + return []int{find[num][0], find[num][1]} + } + } + } + return []int{} +} +``` + +## day187 2024-09-01 +### 2022. Convert 1D Array Into 2D Array +You are given a 0-indexed 1-dimensional (1D) integer array original, and two integers, m and n. You are tasked with creating a 2-dimensional (2D) array with m rows and n columns using all the elements from original. + +The elements from indices 0 to n - 1 (inclusive) of original should form the first row of the constructed 2D array, the elements from indices n to 2 * n - 1 (inclusive) should form the second row of the constructed 2D array, and so on. + +Return an m x n 2D array constructed according to the above procedure, or an empty 2D array if it is impossible. + +![09011WbuX5ihPxx0](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/09011WbuX5ihPxx0.png) + +### 题解 +本题是一道简单题, 先判断能否转换成要求的形状的二维数组, 再构造这个二维数组即可, 判断也很直接, 获取一维数组的长度和要求的二维数组的行列数的乘积比较, 不相等就不能转换. 本题如果出现在面试肯定是道送分题. + +### 代码 +```go +func construct2DArray(original []int, m int, n int) [][]int { + if len(original)!=m*n{ + return [][]int{} + } + result := [][]int{} + for i:=0;i= 10{ + rawnum += convertbyte/10+convertbyte%10 + }else{ + rawnum += convertbyte + } + } + result := rawnum + for i:=0;i 0{ + result += rawnum%10 + rawnum = rawnum/10 + } + if result < 10{ + break + } + rawnum = result + } + return result +} +``` + +## day190 2024-09-04 +### 874. Walking Robot Simulation +A robot on an infinite XY-plane starts at point (0, 0) facing north. The robot can receive a sequence of these three possible types of commands: + +-2: Turn left 90 degrees. +-1: Turn right 90 degrees. +1 <= k <= 9: Move forward k units, one unit at a time. +Some of the grid squares are obstacles. The ith obstacle is at grid point obstacles[i] = (xi, yi). If the robot runs into an obstacle, then it will instead stay in its current location and move on to the next command. + +Return the maximum Euclidean distance that the robot ever gets from the origin squared (i.e. if the distance is 5, return 25). + +Note: + +North means +Y direction. +East means +X direction. +South means -Y direction. +West means -X direction. +There can be obstacle in [0,0]. + +![0904tWTRM0Is4AwD](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0904tWTRM0Is4AwD.png) + +### 题解 +本题按照题目中的要求遍历数组并对机器人的行动进行模拟, 每当一次行动结束后计算其到原点的欧氏距离, 并更新最大值.在执行行动过程中如何判断是否碰到障碍物, 对于一个前进k步的行动, 每次前进1步都判断一下当前坐标是否碰到障碍物. 在go中可以使用map作为哈希表, 将障碍物的坐标, 一个固定的长度为2的数组作为键, 布尔值作为值存储. 则在前进过程中同样将当前位置的坐标作为键, 判断是否为障碍物. 如果是障碍物, 直接停止前进. + +### 代码 +```go +func robotSim(commands []int, obstacles [][]int) int { + dx := []int{0, 1, 0, -1} // 北、东、南、西 + dy := []int{1, 0, -1, 0} + x, y := 0, 0 + direction := 0 + maxDistSquared := 0 + + // 创建障碍物集合 + obstacleSet := make(map[[2]int]bool) + for _, obstacle := range obstacles { + obstacleSet[[2]int{obstacle[0], obstacle[1]}] = true + } + + for _, cmd := range commands { + if cmd == -2 { + direction = (direction - 1 + 4) % 4 + } else if cmd == -1 { + direction = (direction + 1) % 4 + } else { + for step := 0; step < cmd; step++ { + nextX, nextY := x + dx[direction], y + dy[direction] + if obstacleSet[[2]int{nextX, nextY}] { + break + } + x, y = nextX, nextY + + } + distSquared := x*x + y*y + if distSquared > maxDistSquared { + maxDistSquared = distSquared + } + } + } + + return maxDistSquared +} + +``` + +### 总结 +本题除了用固定长度的数组作为哈希表的键值, 也可以将x,y坐标通过某种方式哈希后映射到一个唯一的哈希值, 从而将这个哈希值作为键值. 比较简单的哈希方法为将y乘以一个倍数再与x相加, 只要让得到的和不重复(哈希不碰撞)即可. 倍数可以取x,y坐标的最大值. 核心在于让得到的哈希值唯一且对于同样的输入能得到相同的哈希值. + +## day191 2024-09-05 +### 2028. Find Missing Observations +You have observations of n + m 6-sided dice rolls with each face numbered from 1 to 6. n of the observations went missing, and you only have the observations of m rolls. Fortunately, you have also calculated the average value of the n + m rolls. + +You are given an integer array rolls of length m where rolls[i] is the value of the ith observation. You are also given the two integers mean and n. + +Return an array of length n containing the missing observations such that the average value of the n + m rolls is exactly mean. If there are multiple valid answers, return any of them. If no such array exists, return an empty array. + +The average value of a set of k numbers is the sum of the numbers divided by k. + +Note that mean is an integer, so the sum of the n + m rolls should be divisible by n + m. + +![0905t879DaTShf7B](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0905t879DaTShf7B.png) + +### 题解 +本题首先判断什么情况下不存在可行解, 用平均数计算数字和再减去已知的数字后的余数如果比6n大或比n小, 则无可行解, 6n表示未知的n个骰子都是6, n表示未知的n个骰子都是1. 若有可行解, 则先将这n个骰子均赋值为1, 随后使用贪心赋值, 直到将余数用完. 如3个骰子, 先赋值1,1,1. 若余数为7, 则再赋值6,1,1, 再赋值6,3,1. 每次都尽可能将一个骰子赋予最大值. + +### 代码 +```go +func missingRolls(rolls []int, mean int, n int) []int { + rollsum := 0 + for _,roll := range rolls{ + rollsum += roll + } + sum := mean *(len(rolls)+n) + sum -= rollsum + if sum > 6*n || sum < n{ + return []int{} + } + remain := make([]int, n) + for i:=0;i 5{ + remain[index] += 5 + sum -= 5 + index++ + } + remain[index] += sum + return remain +} +``` + +## day192 2024-09-06 +### 3217. Delete Nodes From Linked List Present in Array +You are given an array of integers nums and the head of a linked list. Return the head of the modified linked list after removing all nodes from the linked list that have a value that exists in nums. + +![0906o1q2BrIlssFL](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0906o1q2BrIlssFL.png) + +### 题解 +本题先保存nums中都有哪些数字, 本题的关键在于如何快速判断某个数是否在nums中, 考虑到nums范围到10^5. 因此可以直接用10^5的布尔数组, 下标表示对应的数字, true表示存在, false表示不存在. 当然用哈希表也是可以的. 随后用双指针法对链表中的节点进行遍历并删掉包含在nums中的节点即可. + +### 代码 +```go +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ +func modifiedList(nums []int, head *ListNode) *ListNode { + var right, left *ListNode + numbool := make([]bool, 100001) + for _,num := range nums{ + numbool[num] = true + } + left = nil + right = head + for right != nil{ + if numbool[right.Val]{ + if left == nil{ + head = right.Next + }else{ + left.Next = right.Next + } + }else{ + left = right + } + right = right.Next + } + return head +} +```