mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-05 22:59:31 +08:00
Merge branch 'master' of github.com:youngyangyang04/leetcode-master
This commit is contained in:
@ -35,7 +35,7 @@
|
||||
|
||||
四数之和,和[15.三数之和](https://programmercarl.com/0015.三数之和.html)是一个思路,都是使用双指针法, 基本解法就是在[15.三数之和](https://programmercarl.com/0015.三数之和.html) 的基础上再套一层for循环。
|
||||
|
||||
但是有一些细节需要注意,例如: 不要判断`nums[k] > target` 就返回了,三数之和 可以通过 `nums[i] > 0` 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是`[-4, -3, -2, -1]`,`target`是`-10`,不能因为`-4 > -10`而跳过。但是我们依旧可以去做剪枝,逻辑变成`nums[i] > target && (nums[i] >=0 || target >= 0)`就可以了。
|
||||
但是有一些细节需要注意,例如: 不要判断`nums[k] > target` 就返回了,三数之和 可以通过 `nums[i] > 0` 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是`[-4, -3, -2, -1]`,`target`是`-10`,不能因为`-4 > -10`而跳过。但是我们依旧可以去做剪枝,逻辑变成`nums[k] > target && (nums[k] >=0 || target >= 0)`就可以了。
|
||||
|
||||
[15.三数之和](https://programmercarl.com/0015.三数之和.html)的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下标作为双指针,找到nums[i] + nums[left] + nums[right] == 0。
|
||||
|
||||
@ -253,7 +253,7 @@ public class Solution {
|
||||
for (int k = 0; k < nums.length; k++) {
|
||||
// 剪枝处理
|
||||
if (nums[k] > target && nums[k] >= 0) {
|
||||
break;
|
||||
break; // 此处的break可以等价于return result;
|
||||
}
|
||||
// 对nums[k]去重
|
||||
if (k > 0 && nums[k] == nums[k - 1]) {
|
||||
@ -262,7 +262,7 @@ public class Solution {
|
||||
for (int i = k + 1; i < nums.length; i++) {
|
||||
// 第二级剪枝
|
||||
if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
|
||||
break;
|
||||
break; // 注意是break到上一级for循环,如果直接return result;会有遗漏
|
||||
}
|
||||
// 对nums[i]去重
|
||||
if (i > k + 1 && nums[i] == nums[i - 1]) {
|
||||
@ -802,3 +802,4 @@ end
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
</a>
|
||||
|
||||
|
@ -129,6 +129,36 @@ class Solution {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public ListNode removeNthFromEnd(ListNode head, int n) {
|
||||
// 创建一个新的哑节点,指向原链表头
|
||||
ListNode s = new ListNode(-1, head);
|
||||
// 递归调用remove方法,从哑节点开始进行删除操作
|
||||
remove(s, n);
|
||||
// 返回新链表的头(去掉可能的哑节点)
|
||||
return s.next;
|
||||
}
|
||||
|
||||
public int remove(ListNode p, int n) {
|
||||
// 递归结束条件:如果当前节点为空,返回0
|
||||
if (p == null) {
|
||||
return 0;
|
||||
}
|
||||
// 递归深入到下一个节点
|
||||
int net = remove(p.next, n);
|
||||
// 如果当前节点是倒数第n个节点,进行删除操作
|
||||
if (net == n) {
|
||||
p.next = p.next.next;
|
||||
}
|
||||
// 返回当前节点的总深度
|
||||
return net + 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Python:
|
||||
|
||||
```python
|
||||
|
@ -166,12 +166,35 @@ class Solution {
|
||||
deque.pop();
|
||||
}
|
||||
}
|
||||
//最后判断栈中元素是否匹配
|
||||
//遍历结束,如果栈为空,则括号全部匹配
|
||||
return deque.isEmpty();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// 解法二
|
||||
// 对应的另一半一定在栈顶
|
||||
class Solution {
|
||||
public boolean isValid(String s) {
|
||||
Stack<Character> stack = new Stack<>();
|
||||
for(char c : s.toCharArray()){
|
||||
// 有对应的另一半就直接消消乐
|
||||
if(c == ')' && !stack.isEmpty() && stack.peek() == '(')
|
||||
stack.pop();
|
||||
else if(c == '}' && !stack.isEmpty() && stack.peek() == '{')
|
||||
stack.pop();
|
||||
else if(c == ']' && !stack.isEmpty() && stack.peek() == '[')
|
||||
stack.pop();
|
||||
else
|
||||
stack.push(c);// 没有匹配的就放进去
|
||||
}
|
||||
|
||||
return stack.isEmpty();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Python:
|
||||
|
||||
```python
|
||||
@ -555,3 +578,4 @@ impl Solution {
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
</a>
|
||||
|
||||
|
@ -715,26 +715,65 @@ object Solution {
|
||||
### C#:
|
||||
|
||||
```csharp
|
||||
public class Solution {
|
||||
public int[][] GenerateMatrix(int n) {
|
||||
int[][] answer = new int[n][];
|
||||
for(int i = 0; i < n; i++)
|
||||
answer[i] = new int[n];
|
||||
int start = 0;
|
||||
int end = n - 1;
|
||||
int tmp = 1;
|
||||
while(tmp < n * n)
|
||||
{
|
||||
for(int i = start; i < end; i++) answer[start][i] = tmp++;
|
||||
for(int i = start; i < end; i++) answer[i][end] = tmp++;
|
||||
for(int i = end; i > start; i--) answer[end][i] = tmp++;
|
||||
for(int i = end; i > start; i--) answer[i][start] = tmp++;
|
||||
start++;
|
||||
end--;
|
||||
}
|
||||
if(n % 2 == 1) answer[n / 2][n / 2] = tmp;
|
||||
return answer;
|
||||
public int[][] GenerateMatrix(int n)
|
||||
{
|
||||
// 参考Carl的代码随想录里面C++的思路
|
||||
// https://www.programmercarl.com/0059.%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5II.html#%E6%80%9D%E8%B7%AF
|
||||
int startX = 0, startY = 0; // 定义每循环一个圈的起始位置
|
||||
int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
|
||||
int count = 1; // 用来给矩阵每个空格赋值
|
||||
int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
|
||||
int offset = 1;// 需要控制每一条边遍历的长度,每次循环右边界收缩一位
|
||||
|
||||
// 构建result二维数组
|
||||
int[][] result = new int[n][];
|
||||
for (int k = 0; k < n; k++)
|
||||
{
|
||||
result[k] = new int[n];
|
||||
}
|
||||
|
||||
int i = 0, j = 0; // [i,j]
|
||||
while (loop > 0)
|
||||
{
|
||||
i = startX;
|
||||
j = startY;
|
||||
// 四个For循环模拟转一圈
|
||||
// 第一排,从左往右遍历,不取最右侧的值(左闭右开)
|
||||
for (; j < n - offset; j++)
|
||||
{
|
||||
result[i][j] = count++;
|
||||
}
|
||||
// 右侧的第一列,从上往下遍历,不取最下面的值(左闭右开)
|
||||
for (; i < n - offset; i++)
|
||||
{
|
||||
result[i][j] = count++;
|
||||
}
|
||||
|
||||
// 最下面的第一行,从右往左遍历,不取最左侧的值(左闭右开)
|
||||
for (; j > startY; j--)
|
||||
{
|
||||
result[i][j] = count++;
|
||||
}
|
||||
|
||||
// 左侧第一列,从下往上遍历,不取最左侧的值(左闭右开)
|
||||
for (; i > startX; i--)
|
||||
{
|
||||
result[i][j] = count++;
|
||||
}
|
||||
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
|
||||
startX++;
|
||||
startY++;
|
||||
|
||||
// offset 控制每一圈里每一条边遍历的长度
|
||||
offset++;
|
||||
loop--;
|
||||
}
|
||||
if (n % 2 == 1)
|
||||
{
|
||||
// n 为奇数
|
||||
result[mid][mid] = count;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -130,8 +130,8 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
* 时间复杂度:$O(n)$
|
||||
* 空间复杂度:$O(n)$
|
||||
* 时间复杂度:O(n)
|
||||
* 空间复杂度:O(n)
|
||||
|
||||
当然依然也可以,优化一下空间复杂度,代码如下:
|
||||
|
||||
@ -154,8 +154,8 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
* 时间复杂度:$O(n)$
|
||||
* 空间复杂度:$O(1)$
|
||||
* 时间复杂度:O(n)
|
||||
* 空间复杂度:O(1)
|
||||
|
||||
后面将讲解的很多动规的题目其实都是当前状态依赖前两个,或者前三个状态,都可以做空间上的优化,**但我个人认为面试中能写出版本一就够了哈,清晰明了,如果面试官要求进一步优化空间的话,我们再去优化**。
|
||||
|
||||
@ -524,3 +524,4 @@ impl Solution {
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
</a>
|
||||
|
||||
|
@ -1231,6 +1231,47 @@ impl Solution {
|
||||
}
|
||||
```
|
||||
|
||||
#### C#:
|
||||
|
||||
```C# 199.二叉树的右视图
|
||||
public class Solution
|
||||
{
|
||||
public IList<int> RightSideView(TreeNode root)
|
||||
{
|
||||
var result = new List<int>();
|
||||
Queue<TreeNode> queue = new();
|
||||
|
||||
if (root != null)
|
||||
{
|
||||
queue.Enqueue(root);
|
||||
}
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
int count = queue.Count;
|
||||
int lastValue = count - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var currentNode = queue.Dequeue();
|
||||
if (i == lastValue)
|
||||
{
|
||||
result.Add(currentNode.val);
|
||||
}
|
||||
|
||||
// lastValue == i == count -1 : left 先于 right 进入Queue
|
||||
if (currentNode.left != null) queue.Enqueue(currentNode.left);
|
||||
if (currentNode.right != null) queue.Enqueue(currentNode.right);
|
||||
|
||||
//// lastValue == i == 0: right 先于 left 进入Queue
|
||||
// if(currentNode.right !=null ) queue.Enqueue(currentNode.right);
|
||||
// if(currentNode.left !=null ) queue.Enqueue(currentNode.left);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 637.二叉树的层平均值
|
||||
|
||||
[力扣题目链接](https://leetcode.cn/problems/average-of-levels-in-binary-tree/)
|
||||
@ -1558,6 +1599,35 @@ impl Solution {
|
||||
}
|
||||
```
|
||||
|
||||
#### C#:
|
||||
|
||||
```C# 二叉树的层平均值
|
||||
public class Solution {
|
||||
public IList<double> AverageOfLevels(TreeNode root) {
|
||||
var result= new List<double>();
|
||||
Queue<TreeNode> queue = new();
|
||||
if(root !=null) queue.Enqueue(root);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
int count = queue.Count;
|
||||
double value=0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var curentNode=queue.Dequeue();
|
||||
value += curentNode.val;
|
||||
if (curentNode.left!=null) queue.Enqueue(curentNode.left);
|
||||
if (curentNode.right!=null) queue.Enqueue(curentNode.right);
|
||||
}
|
||||
result.Add(value/count);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 429.N叉树的层序遍历
|
||||
|
||||
[力扣题目链接](https://leetcode.cn/problems/n-ary-tree-level-order-traversal/)
|
||||
|
@ -40,7 +40,7 @@
|
||||
本题依然是前序遍历和后序遍历都可以,前序求的是深度,后序求的是高度。
|
||||
|
||||
* 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
|
||||
* 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始)
|
||||
* 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)
|
||||
|
||||
那么使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离 也同样是最小深度。
|
||||
|
||||
|
@ -489,6 +489,67 @@ impl Solution {
|
||||
}
|
||||
```
|
||||
|
||||
### C:
|
||||
|
||||
```c
|
||||
int str_to_int(char *str) {
|
||||
// string转integer
|
||||
int num = 0, tens = 1;
|
||||
for (int i = strlen(str) - 1; i >= 0; i--) {
|
||||
if (str[i] == '-') {
|
||||
num *= -1;
|
||||
break;
|
||||
}
|
||||
num += (str[i] - '0') * tens;
|
||||
tens *= 10;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
int evalRPN(char** tokens, int tokensSize) {
|
||||
|
||||
int *stack = (int *)malloc(tokensSize * sizeof(int));
|
||||
assert(stack);
|
||||
int stackTop = 0;
|
||||
|
||||
for (int i = 0; i < tokensSize; i++) {
|
||||
char symbol = (tokens[i])[0];
|
||||
if (symbol < '0' && (tokens[i])[1] == '\0') {
|
||||
|
||||
// pop两个数字
|
||||
int num1 = stack[--stackTop];
|
||||
int num2 = stack[--stackTop];
|
||||
|
||||
// 计算结果
|
||||
int result;
|
||||
if (symbol == '+') {
|
||||
result = num1 + num2;
|
||||
} else if (symbol == '-') {
|
||||
result = num2 - num1;
|
||||
} else if (symbol == '/') {
|
||||
result = num2 / num1;
|
||||
} else {
|
||||
result = num1 * num2;
|
||||
}
|
||||
|
||||
// push回stack
|
||||
stack[stackTop++] = result;
|
||||
|
||||
} else {
|
||||
|
||||
// push数字进stack
|
||||
int num = str_to_int(tokens[i]);
|
||||
stack[stackTop++] = num;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
int result = stack[0];
|
||||
free(stack);
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
|
@ -639,7 +639,46 @@ func reverse(b []byte) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
//双指针解法。指针逆序遍历,将遍历后得到的单词(间隔为空格,用以区分)顺序放置在额外空间
|
||||
//时间复杂度O(n),空间复杂度O(n)
|
||||
func reverseWords(s string) string {
|
||||
strBytes := []byte(s)
|
||||
n := len(strBytes)
|
||||
// 记录有效字符范围的起始和结束位置
|
||||
start, end := 0, n-1
|
||||
// 去除开头空格
|
||||
for start < n && strBytes[start] == 32 {
|
||||
start++
|
||||
}
|
||||
// 处理全是空格或空字符串情况
|
||||
if start == n {
|
||||
return ""
|
||||
}
|
||||
// 去除结尾空格
|
||||
for end >= 0 && strBytes[end] == 32 {
|
||||
end--
|
||||
}
|
||||
// 结果切片,预分配容量
|
||||
res := make([]byte, 0, end-start+1)//这里挺重要的,本人之前没有预分配容量,每次循环都添加单词,导致内存超限(也可能就是我之前的思路有问题)
|
||||
// 从后往前遍历有效字符范围
|
||||
for i := end; i >= start; {
|
||||
// 找单词起始位置,直接通过循环条件判断定位
|
||||
for ; i >= start && strBytes[i] == 32; i-- {
|
||||
}
|
||||
j := i
|
||||
for ; j >= start && strBytes[j]!= 32; j-- {
|
||||
}
|
||||
res = append(res, strBytes[j+1:i+1]...)
|
||||
// 只在不是最后一个单词时添加空格
|
||||
if j > start {
|
||||
res = append(res, 32)
|
||||
}
|
||||
i = j
|
||||
}
|
||||
return string(res)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### JavaScript:
|
||||
|
@ -1277,6 +1277,95 @@ impl MyStack {
|
||||
}
|
||||
```
|
||||
|
||||
### C:
|
||||
|
||||
> C:单队列
|
||||
|
||||
```c
|
||||
typedef struct Node {
|
||||
int val;
|
||||
struct Node *next;
|
||||
} Node_t;
|
||||
|
||||
// 用单向链表实现queue
|
||||
typedef struct {
|
||||
Node_t *head;
|
||||
Node_t *foot;
|
||||
int size;
|
||||
} MyStack;
|
||||
|
||||
MyStack* myStackCreate() {
|
||||
MyStack *obj = (MyStack *)malloc(sizeof(MyStack));
|
||||
assert(obj);
|
||||
obj->head = NULL;
|
||||
obj->foot = NULL;
|
||||
obj->size = 0;
|
||||
return obj;
|
||||
}
|
||||
|
||||
void myStackPush(MyStack* obj, int x) {
|
||||
|
||||
Node_t *temp = (Node_t *)malloc(sizeof(Node_t));
|
||||
assert(temp);
|
||||
temp->val = x;
|
||||
temp->next = NULL;
|
||||
|
||||
// 添加至queue末尾
|
||||
if (obj->foot) {
|
||||
obj->foot->next = temp;
|
||||
} else {
|
||||
obj->head = temp;
|
||||
}
|
||||
obj->foot = temp;
|
||||
obj->size++;
|
||||
}
|
||||
|
||||
int myStackPop(MyStack* obj) {
|
||||
|
||||
// 获取末尾元素
|
||||
int target = obj->foot->val;
|
||||
|
||||
if (obj->head == obj->foot) {
|
||||
free(obj->foot);
|
||||
obj->head = NULL;
|
||||
obj->foot = NULL;
|
||||
} else {
|
||||
|
||||
Node_t *prev = obj->head;
|
||||
// 移动至queue尾部节点前一个节点
|
||||
while (prev->next != obj->foot) {
|
||||
prev = prev->next;
|
||||
}
|
||||
|
||||
free(obj->foot);
|
||||
obj->foot = prev;
|
||||
obj->foot->next = NULL;
|
||||
}
|
||||
|
||||
obj->size--;
|
||||
return target;
|
||||
}
|
||||
|
||||
int myStackTop(MyStack* obj) {
|
||||
return obj->foot->val;
|
||||
}
|
||||
|
||||
bool myStackEmpty(MyStack* obj) {
|
||||
return obj->size == 0;
|
||||
}
|
||||
|
||||
void myStackFree(MyStack* obj) {
|
||||
Node_t *curr = obj->head;
|
||||
while (curr != NULL) {
|
||||
Node_t *temp = curr->next;
|
||||
free(curr);
|
||||
curr = temp;
|
||||
}
|
||||
free(obj);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
|
@ -99,7 +99,7 @@ if (cur == NULL) return cur;
|
||||
|
||||
* 确定单层递归的逻辑
|
||||
|
||||
在遍历二叉搜索树的时候就是寻找区间[p->val, q->val](注意这里是左闭又闭)
|
||||
在遍历二叉搜索树的时候就是寻找区间[p->val, q->val](注意这里是左闭右闭)
|
||||
|
||||
那么如果 cur->val 大于 p->val,同时 cur->val 大于q->val,那么就应该向左遍历(说明目标区间在左子树上)。
|
||||
|
||||
|
@ -267,7 +267,7 @@ class Solution {
|
||||
//利用双端队列手动实现单调队列
|
||||
/**
|
||||
* 用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
|
||||
* 单调队列类似 (tail -->) 3 --> 2 --> 1 --> 0 (--> head) (右边为头结点,元素存的是下标)
|
||||
* 单调递减队列类似 (head -->) 3 --> 2 --> 1 --> 0 (--> tail) (左边为头结点,元素存的是下标)
|
||||
*/
|
||||
class Solution {
|
||||
public int[] maxSlidingWindow(int[] nums, int k) {
|
||||
@ -281,7 +281,7 @@ class Solution {
|
||||
while(!deque.isEmpty() && deque.peek() < i - k + 1){
|
||||
deque.poll();
|
||||
}
|
||||
// 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
|
||||
// 2.维护单调递减队列:新元素若大于队尾元素,则弹出队尾元素,直到满足单调性
|
||||
while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
|
||||
deque.pollLast();
|
||||
}
|
||||
@ -890,7 +890,40 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
### C
|
||||
|
||||
```c
|
||||
int* maxSlidingWindow(int* nums, int numsSize, int k, int* returnSize) {
|
||||
*returnSize = numsSize - k + 1;
|
||||
int *res = (int*)malloc((*returnSize) * sizeof(int));
|
||||
assert(res);
|
||||
int *deque = (int*)malloc(numsSize * sizeof(int));
|
||||
assert(deque);
|
||||
int front = 0, rear = 0, idx = 0;
|
||||
|
||||
for (int i = 0 ; i < numsSize ; i++) {
|
||||
while (front < rear && deque[front] <= i - k) {
|
||||
front++;
|
||||
}
|
||||
|
||||
while (front < rear && nums[deque[rear - 1]] <= nums[i]) {
|
||||
rear--;
|
||||
}
|
||||
|
||||
deque[rear++] = i;
|
||||
|
||||
if (i >= k - 1) {
|
||||
res[idx++] = nums[deque[front]];
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
</a>
|
||||
|
||||
|
@ -123,6 +123,9 @@ public:
|
||||
### Java:
|
||||
版本一:使用HashSet
|
||||
```Java
|
||||
// 时间复杂度O(n+m+k) 空间复杂度O(n+k)
|
||||
// 其中n是数组nums1的长度,m是数组nums2的长度,k是交集元素的个数
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@ -145,8 +148,15 @@ class Solution {
|
||||
}
|
||||
|
||||
//方法1:将结果集合转为数组
|
||||
|
||||
return resSet.stream().mapToInt(x -> x).toArray();
|
||||
return res.stream().mapToInt(Integer::intValue).toArray();
|
||||
/**
|
||||
* 将 Set<Integer> 转换为 int[] 数组:
|
||||
* 1. stream() : Collection 接口的方法,将集合转换为 Stream<Integer>
|
||||
* 2. mapToInt(Integer::intValue) :
|
||||
* - 中间操作,将 Stream<Integer> 转换为 IntStream
|
||||
* - 使用方法引用 Integer::intValue,将 Integer 对象拆箱为 int 基本类型
|
||||
* 3. toArray() : 终端操作,将 IntStream 转换为 int[] 数组。
|
||||
*/
|
||||
|
||||
//方法2:另外申请一个数组存放setRes中的元素,最后返回数组
|
||||
int[] arr = new int[resSet.size()];
|
||||
@ -538,3 +548,4 @@ end
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
</a>
|
||||
|
||||
|
@ -104,7 +104,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
* 时间复杂度: O(n)
|
||||
* 时间复杂度: O(m+n),其中m表示ransomNote的长度,n表示magazine的长度
|
||||
* 空间复杂度: O(1)
|
||||
|
||||
|
||||
@ -470,3 +470,4 @@ bool canConstruct(char* ransomNote, char* magazine) {
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
</a>
|
||||
|
||||
|
@ -390,6 +390,8 @@ public:
|
||||
|
||||
### Java:
|
||||
|
||||
(版本一) 前缀表 减一
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public boolean repeatedSubstringPattern(String s) {
|
||||
@ -420,6 +422,45 @@ class Solution {
|
||||
}
|
||||
```
|
||||
|
||||
(版本二) 前缀表 不减一
|
||||
|
||||
```java
|
||||
/*
|
||||
* 充分条件:如果字符串s是由重复子串组成的,那么它的最长相等前后缀不包含的子串一定是s的最小重复子串。
|
||||
* 必要条件:如果字符串s的最长相等前后缀不包含的子串是s的最小重复子串,那么s必然是由重复子串组成的。
|
||||
* 推得:当字符串s的长度可以被其最长相等前后缀不包含的子串的长度整除时,不包含的子串就是s的最小重复子串。
|
||||
*
|
||||
* 时间复杂度:O(n)
|
||||
* 空间复杂度:O(n)
|
||||
*/
|
||||
class Solution {
|
||||
public boolean repeatedSubstringPattern(String s) {
|
||||
// if (s.equals("")) return false;
|
||||
// 边界判断(可以去掉,因为题目给定范围是1 <= s.length <= 10^4)
|
||||
int n = s.length();
|
||||
|
||||
// Step 1.构建KMP算法的前缀表
|
||||
int[] next = new int[n]; // 前缀表的值表示 以该位置结尾的字符串的最长相等前后缀的长度
|
||||
int j = 0;
|
||||
next[0] = 0;
|
||||
for (int i = 1; i < n; i++) {
|
||||
while (j > 0 && s.charAt(i) != s.charAt(j)) // 只要前缀后缀还不一致,就根据前缀表回退j直到起点为止
|
||||
j = next[j - 1];
|
||||
if (s.charAt(i) == s.charAt(j))
|
||||
j++;
|
||||
next[i] = j;
|
||||
}
|
||||
|
||||
// Step 2.判断重复子字符串
|
||||
if (next[n - 1] > 0 && n % (n - next[n - 1]) == 0) { // 当字符串s的长度可以被其最长相等前后缀不包含的子串的长度整除时
|
||||
return true; // 不包含的子串就是s的最小重复子串
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Python:
|
||||
|
||||
(版本一) 前缀表 减一
|
||||
@ -648,6 +689,29 @@ var repeatedSubstringPattern = function (s) {
|
||||
};
|
||||
```
|
||||
|
||||
> 正则匹配
|
||||
```javascript
|
||||
/**
|
||||
* @param {string} s
|
||||
* @return {boolean}
|
||||
*/
|
||||
var repeatedSubstringPattern = function(s) {
|
||||
let reg = /^(\w+)\1+$/
|
||||
return reg.test(s)
|
||||
};
|
||||
```
|
||||
> 移动匹配
|
||||
```javascript
|
||||
/**
|
||||
* @param {string} s
|
||||
* @return {boolean}
|
||||
*/
|
||||
var repeatedSubstringPattern = function (s) {
|
||||
let ss = s + s;
|
||||
return ss.substring(1, ss.length - 1).includes(s);
|
||||
};
|
||||
```
|
||||
|
||||
### TypeScript:
|
||||
|
||||
> 前缀表统一减一
|
||||
@ -853,8 +917,10 @@ impl Solution {
|
||||
}
|
||||
```
|
||||
### C#
|
||||
|
||||
> 前缀表不减一
|
||||
|
||||
```csharp
|
||||
// 前缀表不减一
|
||||
public bool RepeatedSubstringPattern(string s)
|
||||
{
|
||||
if (s.Length == 0)
|
||||
@ -879,9 +945,61 @@ public int[] GetNext(string s)
|
||||
}
|
||||
```
|
||||
|
||||
> 移动匹配
|
||||
```csharp
|
||||
public bool RepeatedSubstringPattern(string s) {
|
||||
string ss = (s + s).Substring(1, (s + s).Length - 2);
|
||||
return ss.Contains(s);
|
||||
}
|
||||
```
|
||||
### C
|
||||
|
||||
```c
|
||||
// 前缀表不减一
|
||||
int *build_next(char* s, int len) {
|
||||
|
||||
int *next = (int *)malloc(len * sizeof(int));
|
||||
assert(next);
|
||||
|
||||
// 初始化前缀表
|
||||
next[0] = 0;
|
||||
|
||||
// 构建前缀表表
|
||||
int i = 1, j = 0;
|
||||
while (i < len) {
|
||||
if (s[i] == s[j]) {
|
||||
j++;
|
||||
next[i] = j;
|
||||
i++;
|
||||
} else if (j > 0) {
|
||||
j = next[j - 1];
|
||||
} else {
|
||||
next[i] = 0;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
bool repeatedSubstringPattern(char* s) {
|
||||
|
||||
int len = strlen(s);
|
||||
int *next = build_next(s, len);
|
||||
bool result = false;
|
||||
|
||||
// 检查最小重复片段能否被长度整除
|
||||
if (next[len - 1]) {
|
||||
result = len % (len - next[len - 1]) == 0;
|
||||
}
|
||||
|
||||
free(next);
|
||||
return result;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
</a>
|
||||
|
||||
|
@ -670,18 +670,26 @@ class Solution:
|
||||
|
||||
# 创建二维动态规划数组,行表示选取的元素数量,列表示累加和
|
||||
dp = [[0] * (target_sum + 1) for _ in range(len(nums) + 1)]
|
||||
dp = [[0] * (target_sum + 1) for _ in range(len(nums))]
|
||||
|
||||
# 初始化状态
|
||||
dp[0][0] = 1
|
||||
if nums[0] <= target_sum:
|
||||
dp[0][nums[0]] = 1
|
||||
numZero = 0
|
||||
for i in range(len(nums)):
|
||||
if nums[i] == 0:
|
||||
numZero += 1
|
||||
dp[i][0] = int(math.pow(2, numZero))
|
||||
|
||||
# 动态规划过程
|
||||
for i in range(1, len(nums) + 1):
|
||||
for i in range(1, len(nums)):
|
||||
for j in range(target_sum + 1):
|
||||
dp[i][j] = dp[i - 1][j] # 不选取当前元素
|
||||
if j >= nums[i - 1]:
|
||||
dp[i][j] += dp[i - 1][j - nums[i - 1]] # 选取当前元素
|
||||
dp[i][j] += dp[i - 1][j - nums[i]] # 选取当前元素
|
||||
|
||||
return dp[len(nums)][target_sum] # 返回达到目标和的方案数
|
||||
return dp[len(nums)-1][target_sum] # 返回达到目标和的方案数
|
||||
|
||||
|
||||
```
|
||||
|
@ -546,7 +546,7 @@ object Solution {
|
||||
}
|
||||
}
|
||||
```
|
||||
## C
|
||||
### C
|
||||
|
||||
```c
|
||||
int change(int amount, int* coins, int coinsSize) {
|
||||
@ -593,37 +593,3 @@ public class Solution
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
</a>
|
||||
|
||||
----------
|
||||
|
||||
|
||||
|
||||
回归本题,动规五步曲来分析如下:
|
||||
|
||||
1. 确定dp数组以及下标的含义
|
||||
|
||||
dp[j]:凑成总金额j的货币组合数为dp[j]
|
||||
|
||||
2. 确定递推公式
|
||||
|
||||
dp[j] 就是所有的dp[j - coins[i]](考虑coins[i]的情况)相加。
|
||||
|
||||
所以递推公式:dp[j] += dp[j - coins[i]];
|
||||
|
||||
**这个递推公式大家应该不陌生了,我在讲解01背包题目的时候在这篇[494. 目标和](https://programmercarl.com/0494.目标和.html)中就讲解了,求装满背包有几种方法,公式都是:dp[j] += dp[j - nums[i]];**
|
||||
|
||||
3. dp数组如何初始化
|
||||
|
||||
首先dp[0]一定要为1,dp[0] = 1是 递归公式的基础。如果dp[0] = 0 的话,后面所有推导出来的值都是0了。
|
||||
|
||||
那么 dp[0] = 1 有没有含义,其实既可以说 凑成总金额0的货币组合数为1,也可以说 凑成总金额0的货币组合数为0,好像都没有毛病。
|
||||
|
||||
但题目描述中,也没明确说 amount = 0 的情况,结果应该是多少。
|
||||
|
||||
这里我认为题目描述还是要说明一下,因为后台测试数据是默认,amount = 0 的情况,组合数为1的。
|
||||
|
||||
下标非0的dp[j]初始化为0,这样累计加dp[j - coins[i]]的时候才不会影响真正的dp[j]
|
||||
|
||||
dp[0]=1还说明了一种情况:如果正好选了coins[i]后,也就是j-coins[i] == 0的情况表示这个硬币刚好能选,此时dp[0]为1表示只选coins[i]存在这样的一种选法。
|
||||
|
||||
----------------
|
||||
|
@ -273,22 +273,20 @@ class Solution:
|
||||
### Go
|
||||
```go
|
||||
func monotoneIncreasingDigits(n int) int {
|
||||
s := strconv.Itoa(N)//将数字转为字符串,方便使用下标
|
||||
ss := []byte(s)//将字符串转为byte数组,方便更改。
|
||||
n := len(ss)
|
||||
if n <= 1 {
|
||||
return n
|
||||
}
|
||||
for i := n-1; i > 0; i-- {
|
||||
if ss[i-1] > ss[i] { //前一个大于后一位,前一位减1,后面的全部置为9
|
||||
ss[i-1] -= 1
|
||||
for j := i; j < n; j++ { //后面的全部置为9
|
||||
ss[j] = '9'
|
||||
}
|
||||
}
|
||||
}
|
||||
res, _ := strconv.Atoi(string(ss))
|
||||
return res
|
||||
s := strconv.Itoa(n)
|
||||
// 从左到右遍历字符串,找到第一个不满足单调递增的位置
|
||||
for i := len(s) - 2; i >= 0; i-- {
|
||||
if s[i] > s[i+1] {
|
||||
// 将该位置的数字减1
|
||||
s = s[:i] + string(s[i]-1) + s[i+1:]
|
||||
// 将该位置之后的所有数字置为9
|
||||
for j := i + 1; j < len(s); j++ {
|
||||
s = s[:j] + "9" + s[j+1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
result, _ := strconv.Atoi(s)
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
@ -447,3 +445,4 @@ public class Solution
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
</a>
|
||||
|
||||
|
@ -212,13 +212,14 @@ public class Main {
|
||||
int horizontalCut = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
horizontalCut += horizontal[i];
|
||||
result = Math.min(result, Math.abs(sum - 2 * horizontalCut));
|
||||
result = Math.min(result, Math.abs((sum - horizontalCut) - horizontalCut));
|
||||
// 更新result。其中,horizontalCut表示前i行的和,sum - horizontalCut表示剩下的和,作差、取绝对值,得到题目需要的“A和B各自的子区域内的土地总价值之差”。下同。
|
||||
}
|
||||
|
||||
int verticalCut = 0;
|
||||
for (int j = 0; j < m; j++) {
|
||||
verticalCut += vertical[j];
|
||||
result = Math.min(result, Math.abs(sum - 2 * verticalCut));
|
||||
result = Math.min(result, Math.abs((sum - verticalCut) - verticalCut));
|
||||
}
|
||||
|
||||
System.out.println(result);
|
||||
@ -526,3 +527,89 @@ int main()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Go
|
||||
|
||||
前缀和
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"bufio"
|
||||
"strings"
|
||||
"strconv"
|
||||
"math"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var n, m int
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
line, _ := reader.ReadString('\n')
|
||||
line = strings.TrimSpace(line)
|
||||
params := strings.Split(line, " ")
|
||||
|
||||
n, _ = strconv.Atoi(params[0])
|
||||
m, _ = strconv.Atoi(params[1])//n和m读取完成
|
||||
|
||||
land := make([][]int, n)//土地矩阵初始化
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
line, _ := reader.ReadString('\n')
|
||||
line = strings.TrimSpace(line)
|
||||
values := strings.Split(line, " ")
|
||||
land[i] = make([]int, m)
|
||||
for j := 0; j < m; j++ {
|
||||
value, _ := strconv.Atoi(values[j])
|
||||
land[i][j] = value
|
||||
}
|
||||
}//所有读取完成
|
||||
|
||||
//初始化前缀和矩阵
|
||||
preMatrix := make([][]int, n+1)
|
||||
for i := 0; i <= n; i++ {
|
||||
preMatrix[i] = make([]int, m+1)
|
||||
}
|
||||
|
||||
for a := 1; a < n+1; a++ {
|
||||
for b := 1; b < m+1; b++ {
|
||||
preMatrix[a][b] = land[a-1][b-1] + preMatrix[a-1][b] + preMatrix[a][b-1] - preMatrix[a-1][b-1]
|
||||
}
|
||||
}
|
||||
|
||||
totalSum := preMatrix[n][m]
|
||||
|
||||
minDiff := math.MaxInt32//初始化极大数,用于比较
|
||||
|
||||
//按行分割
|
||||
for i := 1; i < n; i++ {
|
||||
topSum := preMatrix[i][m]
|
||||
|
||||
bottomSum := totalSum - topSum
|
||||
|
||||
diff := int(math.Abs(float64(topSum - bottomSum)))
|
||||
if diff < minDiff {
|
||||
minDiff = diff
|
||||
}
|
||||
}
|
||||
|
||||
//按列分割
|
||||
for j := 1; j < m; j++ {
|
||||
topSum := preMatrix[n][j]
|
||||
|
||||
bottomSum := totalSum - topSum
|
||||
|
||||
diff := int(math.Abs(float64(topSum - bottomSum)))
|
||||
if diff < minDiff {
|
||||
minDiff = diff
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(minDiff)
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -238,7 +238,7 @@ class Solution {
|
||||
while (!st.empty()) {
|
||||
TreeNode node = st.peek();
|
||||
if (node != null) {
|
||||
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
|
||||
st.pop(); // 将该节点弹出,避免重复操作,下面再将右左中节点添加到栈中(前序遍历-中左右,入栈顺序右左中)
|
||||
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
|
||||
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
|
||||
st.push(node); // 添加中节点
|
||||
@ -266,11 +266,10 @@ public List<Integer> inorderTraversal(TreeNode root) {
|
||||
while (!st.empty()) {
|
||||
TreeNode node = st.peek();
|
||||
if (node != null) {
|
||||
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
|
||||
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中(中序遍历-左中右,入栈顺序右中左)
|
||||
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
|
||||
st.push(node); // 添加中节点
|
||||
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
|
||||
|
||||
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
|
||||
} else { // 只有遇到空节点的时候,才将下一个节点放进结果集
|
||||
st.pop(); // 将空节点弹出
|
||||
@ -294,7 +293,7 @@ class Solution {
|
||||
while (!st.empty()) {
|
||||
TreeNode node = st.peek();
|
||||
if (node != null) {
|
||||
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
|
||||
st.pop(); // 将该节点弹出,避免重复操作,下面再将中右左节点添加到栈中(后序遍历-左右中,入栈顺序中右左)
|
||||
st.push(node); // 添加中节点
|
||||
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
|
||||
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
|
||||
@ -975,3 +974,4 @@ public IList<int> PostorderTraversal(TreeNode root)
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
</a>
|
||||
|
||||
|
@ -316,7 +316,51 @@ print(knapsack(n, bag_weight, weight, value))
|
||||
|
||||
### JavaScript
|
||||
|
||||
```js
|
||||
const readline = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
let input = [];
|
||||
readline.on('line', (line) => {
|
||||
input.push(line.trim());
|
||||
});
|
||||
|
||||
readline.on('close', () => {
|
||||
// 第一行解析 n 和 v
|
||||
const [n, bagweight] = input[0].split(' ').map(Number);
|
||||
|
||||
/// 剩余 n 行解析重量和价值
|
||||
const weight = [];
|
||||
const value = [];
|
||||
for (let i = 1; i <= n; i++) {
|
||||
const [wi, vi] = input[i].split(' ').map(Number);
|
||||
weight.push(wi);
|
||||
value.push(vi);
|
||||
}
|
||||
|
||||
|
||||
let dp = Array.from({ length: n }, () => Array(bagweight + 1).fill(0));
|
||||
|
||||
for (let j = weight[0]; j <= bagweight; j++) {
|
||||
dp[0][j] = dp[0][j-weight[0]] + value[0];
|
||||
}
|
||||
|
||||
for (let i = 1; i < n; i++) {
|
||||
for (let j = 0; j <= bagweight; j++) {
|
||||
if (j < weight[i]) {
|
||||
dp[i][j] = dp[i - 1][j];
|
||||
} else {
|
||||
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(dp[n - 1][bagweight]);
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
|
Reference in New Issue
Block a user