目录
- 1 序列和
- 2 两数相加
- 3 删除链表的倒数第N个节点
- 4 找树左下角的值
- 5 三数之和
- 6 两数相除
- 7 合并两个有序列表
- 8 无重复字符的最长子串
- 9 二叉树的中序遍历
- 10 最大子序和
- 11 跳跃游戏 II
- 12 验证二叉搜索树
- 13 最低票价
- 14 另一个树的子树
- 15 最大正方形
- 16 x的平方根
- 17 二叉树的最近公共祖先
- 18 pow(x, n)
- 19 只出现一次的数
- 20 和为K的子数组
- 21 K 个一组翻转链表
- 22 课程表2
- 23 乘积最大子数组
- 24 验证回文字符串2
- 25 每个元音包含偶数次的最长子字符串
- 26 寻找重复数
- 27 和可被 K 整除的子数组
- 28 字符串解码
- 29 打家劫舍
- 30 柱状图中的最大矩形
- 31 对称二叉树
- End 基础支持
1 序列和
2020-4-27
题目描述
给出一个正整数N和长度L,找出一段长度大于等于L的连续非负整数,他们的和恰好为N。答案可能有多个,我们需要找出长度最小的那个。
例如 N = 18 L = 2:
5 + 6 + 7 = 18
3 + 4 + 5 + 6 = 18
都是满足要求的,但是我们输出更短的 5 6 7
输入描述:
输入数据包括一行: 两个正整数N(1 ≤ N ≤ 1000000000),L(2 ≤ L ≤ 100)
输出描述:
从小到大输出这段连续非负整数,以空格分隔,行末无空格。如果没有这样的序列或者找出的序列长度大于100,则输出No
示例
输入
18 2
输出
5 6 7
解题思路:
等差数列前n项和
S
n
=
n
(
a
1
+
a
n
)
2
=
n
(
a
1
+
a
1
+
(
n
−
1
)
d
)
2
S_n=\frac{n(a_1+a_n)}{2}=\frac{n(a_1+a_1+(n-1)d)}{2}
Sn=2n(a1+an)=2n(a1+a1+(n−1)d)
当
d
=
1
d=1
d=1时,
S
n
=
n
(
a
1
+
a
n
)
2
=
n
(
2
a
1
+
n
−
1
)
2
=
N
S_n=\frac{n(a_1+a_n)}{2}=\frac{n(2a_1+n-1)}{2}=N
Sn=2n(a1+an)=2n(2a1+n−1)=N
a
1
=
2
N
+
n
−
n
2
2
n
a_1=\frac{2N+n-n^2}{2n}
a1=2n2N+n−n2
其中,
a
1
a_1
a1是序列开始的值,n是序列的长度,则可以对序列的长度从L到100循环,如果对应的n代入上式求得的
a
1
a_1
a1为整数,则满足条件,且第一个满足条件的就是长度最小的那组。
C代码:
#include<stdio.h>
#include<iostream>
using namespace std;
int MinSum(int N, int L)
{
for (int i = L; i <= 100; i++)
{
if ((2 * N + i - i * i) % (2 * i) == 0&&(2*N-i*(i-1))/(2*i)>=0)
{
int s = (2 * N + i - i * i) / (2 * i);
for (int j = 0; j < i-1; j++)
cout << s+j << " ";
cout<<s+i-1;//保证行末无空格
return 0;
}
}
cout << "No";
return 0;
}
int main()
{
int N, L;
cin >> N >>L;
//cout << N << L;
MinSum(N,L);
return 0;
}
回顾
- 善于使用取商与取余
- 选取最小的一组不一定要找出所有的情况再选择,当情况较多时考虑曲线救国
- 注意“行末无空格”的细节输出处理
2 两数相加
2020-4-28
给出两个非空的链表用来表示两个非负的整数。其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储一位数字。我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
我的VS运行版本:
#include<stdio.h>
#include<iostream>
using namespace std;
struct ListNode {
int val; //数据域
struct ListNode* next; //指针域,数据类型为指针
};
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2)
{
struct ListNode* head = (ListNode*) calloc(1, sizeof(ListNode));
struct ListNode* cur = head;
int carry = 0,x=0,y=0,sum=0,k=0; //进位carry初始化为0
ListNode* p = l1; //将p q分别初始化为列表的头部
ListNode* q = l2;
//若其中一个链表为空,则直接返回另一个列表
if (!p)
return q;
if (!q)
return p;
while (p||q||carry==1)
{
if (p)
{
x = p->val;
p = p->next;
}
else
x = 0;
if (q)
{
y = q->val;
q = q->next;
}
else
y = 0;
sum = x + y + carry;
carry = sum / 10; //商
k= sum %10; //余数
cur->next= (ListNode*)calloc(1, sizeof(ListNode));
cur->next->val = k;
cur = cur->next;
}
cur->next = NULL;
return head;
}
struct ListNode* initList(int n)
{
ListNode* p = (ListNode*)malloc(sizeof(ListNode));//创建一个头结点
ListNode* temp = p;//声明一个指针指向头结点,
int key;
for (int i = 0; i < n; i++) {
ListNode* a = (ListNode*)malloc(sizeof(ListNode));//申请一个新节点
cin >> key;
a->val = key;
a->next = NULL;
temp->next = a;
temp = temp->next;
}
return p->next;
}
void display(ListNode* p)
{
ListNode* temp = p;//将temp指针重新指向头结点
while (temp->next)
{
temp = temp->next;
printf("%d", temp->val);
}
printf("\n");
}
int main()
{
int m, n;
cin >> m;
ListNode* l1 = initList(m);
cin >> n;
ListNode* l2 = initList(n);
ListNode* l = addTwoNumbers(l1, l2);
display(l);
}
leetcode AC版本:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2)
{
struct ListNode* head = (struct ListNode*) calloc(1, sizeof(struct ListNode));
struct ListNode* cur = head;
struct ListNode* p = l1; //将p q分别初始化为列表的头部
struct ListNode* q = l2;
int carry = 0,x=0,y=0,sum=0,k=0; //进位carry初始化为0
if (!p)
return q;
if (!q)
return p;
while (p||q||carry==1)
{
if (p)
{
x = p->val;
p = p->next;
}
else
x = 0;
if (q)
{
y = q->val;
q = q->next;
}
else
y = 0;
sum = x + y + carry;
carry = sum / 10; //商
k= sum %10; //余数
cur->next= (struct ListNode*)calloc(1, sizeof(struct ListNode));
cur->next->val = k;
cur = cur->next;
}
cur->next = NULL;
return head->next;
}
回顾:
- 在不输入链表长度情况下输入链表(存疑)
- 为何在我的VS中使用ListNode*就可以声明指针,而在LeetCode中需要加struct(存疑)
- 需要考虑的细节:首位进位问题、两列表长度不等时的情况;
- LeetCode版本中结构体的初始化是没有定义头结点,所以head->next表示第一个结点,而在VS版本中head即第一个结点
3 删除链表的倒数第N个节点
2020-4-29
题目描述:
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
leetcode AC版本:
/**
* Definition for singly-linked list.
struct ListNode {
int val;
struct ListNode *next;
};*/
int count(struct ListNode* L)
{
struct ListNode* p = (struct ListNode*)calloc(1, sizeof(struct ListNode));
p = L;
int i = 0;
do {
i++;
p = p->next
} while (p);
return i;
}
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
int length=count(head);
struct ListNode* dummyHead = (struct ListNode*) calloc (1, sizeof(struct ListNode));
dummyHead=head;
int i=1;
while(head)
{
if(length-n==0)
{
dummyHead=head->next;
break;
}
if(i==(length-n))
{
head->next=head->next->next;
break;
}
i++;
head=head->next;
}
return dummyHead;
}
结合题解修改之后的版本
int count(struct ListNode* L)
{
int i = 0;
do {
i++;
L = L->next;
} while (L);
return i;
}
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
int length=count(head);
struct ListNode* dummyHead = (struct ListNode*) calloc (1, sizeof(struct ListNode));
dummyHead->next=head;
struct ListNode* p=dummyHead;
n=length-n;
while(n>0)
{
n--;
p=p->next;
}
p->next=p->next->next;
return dummyHead->next;
}
双指针一次遍历解法
/**
* Definition for singly-linked list.
struct ListNode {
int val;
struct ListNode *next;
};*/
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
struct ListNode* dummyhead= (struct ListNode*) calloc (1, sizeof(struct ListNode));;
dummyhead->next=head;
struct ListNode* first=dummyhead;
struct ListNode* second=dummyhead;
for(int i=1;i<=n+1;i++)
{
first=first->next;
}
while(first)
{
first=first->next;
second=second->next;
}
second->next=second->next->next;
return dummyhead->next;
}
回顾
- 修改后的代码中,在count函数使用do…while循环时,执行耗时0ms,使用while循环时执行耗时4ms(存疑)
- 利用虚拟头指针,尤其是对于删除第一个节点时大有进益
- 双指针NB
4 找树左下角的值
2020-4-30
给定一个二叉树,在树的最后一行找到最左边的值。示例 :
输入:
1
/ \
2 3
/ / \
4 5 6
/
7
输出:
7
leetcode AC版本:
将层次遍历改为先右子树后左子树,则最后一个出队的值即为所求
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* left;
* struct TreeNode *right;
* };
*/
#define MaxSize 10000
typedef struct queue {
struct TreeNode* data[MaxSize];
int front;
int rear;
}Queue;
Queue Q;
void initilize() {
Q.front = 0;
Q.rear = 0;
}
void Push(struct TreeNode* root) {
Q.data[++Q.rear] = root;
}
struct TreeNode* Pop() {
return Q.data[++Q.front];
}
int empty() {
return Q.rear == Q.front;
}
int findBottomLeftValue(struct TreeNode* root)
{
struct TreeNode *temp= (struct TreeNode*) calloc (1, sizeof(struct TreeNode));
if (root == NULL) return 0;
Push(root);
while (!empty()) {
temp = Pop();
if (temp->right)
Push(temp->right);
if (temp->left)
Push(temp->left);
}
return temp->val;
}
5 三数之和
2020-5-1
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],满足要求的三元组集合为:[[-1, 0, 1],[-1, -1, 2]]
作者:wu_yan_zu
链接:https://leetcode-cn.com/problems/3sum/solution/pai-xu-shuang-zhi-zhen-zhu-xing-jie-shi-python3-by/
leetcode AC版本:
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
void swap(int arr[], int low, int high)
{
int temp;
temp = arr[low];
arr[low] = arr[high];
arr[high] = temp;
}
int Partition(int array[], int low, int high) {
int base = array[low];
while (low < high) {
while (low < high && array[high] >= base) {
high--;
}
swap(array, low, high);//array[low] = array[high];
while (low < high && array[low] <= base) {
low++;
}
swap(array, low, high);//array[high] = array[low];
}
array[low] = base;
return low;
}
void QuickSort(int array[], int low, int high) {
if (low < high) {
int base = Partition(array, low, high);
QuickSort(array, low, base - 1);
QuickSort(array, base + 1, high);
}
}
int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes)
{
*returnSize = 0; // 参数returnSize用来作为二维数组行数下标的指针
if (numsSize < 3)
{
return NULL;
}
QuickSort(nums, 0, numsSize - 1);
int L = 0, R = 0;
int** returnArray = (int**)malloc(sizeof(int*) * numsSize * numsSize);
*returnColumnSizes = (int*)malloc(sizeof(int) * (numsSize) * (numsSize));
for (int i = 0; i < numsSize-2; i++)
{
if (nums[i] > 0)
{
return returnArray;
}
L = i + 1;
R = numsSize - 1;
while (L < R)
{
if (nums[i] + nums[L] + nums[R] == 0)
{
returnArray[*returnSize] = (int*)malloc(sizeof(int) * 3); // 每次找到一组,二级指针就分配三个空,用的时候申请,可以节省空间
(*returnColumnSizes)[*returnSize] = 3;
returnArray[*returnSize][0] = nums[i];
returnArray[*returnSize][1] = nums[L];
returnArray[*returnSize][2] = nums[R];
(*returnSize)++;
while (nums[L + 1] == nums[L] &&(L+1)< R)
{
L++;
}
while (nums[R - 1] == nums[R] && L < (R-1))
{
R--;
}
L++;
R--;
}
else if (nums[i] + nums[L] + nums[R] < 0)
{
L++;
}
else //if(nums[i]+nums[L]+nums[R]>0)
{
R--;
}
}
while ((nums[i]==nums[i+1])&&(i<numsSize-2))
{
i++;
}
}
return returnArray;
}
库函数qsort
https://www.cnblogs.com/xinlovedai/p/6233383.html
功能: 快速排序
头文件:stdlib.h
用法: void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));
参数:
1 待排序数组首元素的地址
2 数组中待排序元素数量
3 各元素的占用空间大小
4 指向函数的指针,用于确定排序的顺序
注意:数组中可以存储数字,字符,或者结构体都行。
使用库函数进行排序的版本
int compInc(const void *a, const void *b) //递增
{
return *(int *)a - *(int *)b;
}
int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes)
{
*returnSize = 0; // 参数returnSize用来作为二维数组行数下标的指针
if (numsSize < 3)
{
return NULL;
}
qsort(nums, numsSize, sizeof(int), compInc);
int L = 0, R = 0;
int maxSize=numsSize*6; //试出来的6
int** returnArray = (int**)malloc(sizeof(int*) * maxSize);
*returnColumnSizes = (int*)malloc(sizeof(int) * maxSize);
for (int i = 0; i < numsSize-2; i++)
{
if (nums[i] > 0)
{
return returnArray;
}
L = i + 1;
R = numsSize - 1;
while (L < R)
{
if (nums[i] + nums[L] + nums[R] == 0)
{
returnArray[*returnSize] = (int*)malloc(sizeof(int) * 3);
(*returnColumnSizes)[*returnSize] = 3;
returnArray[*returnSize][0] = nums[i];
returnArray[*returnSize][1] = nums[L];
returnArray[*returnSize][2] = nums[R];
(*returnSize)++;
while (nums[L + 1] == nums[L] &&(L+1)< R)
{
L++;
}
while (nums[R - 1] == nums[R] && L < (R-1))
{
R--;
}
L++;
R--;
}
else if (nums[i] + nums[L] + nums[R] < 0)
{
L++;
}
else
{
R--;
}
}
while ((nums[i]==nums[i+1])&&(i<numsSize-2))
{
i++;
}
}
return returnArray;
}
复杂度分析
排序:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
i
i
i的遍历:
O
(
n
)
O(n)
O(n),L和R的双遍历:
O
(
n
)
O(n)
O(n)
O
(
n
l
o
g
n
)
+
O
(
n
)
∗
O
(
n
)
=
O
(
n
2
)
O(nlogn)+O(n)*O(n)=O(n^2)
O(nlogn)+O(n)∗O(n)=O(n2)
回顾
- 考虑到一些特殊情况,写在所有操作前面,如本题中数组长度小于3时直接返回NULL
- 去重是本题的关键点,主要利用排序后的数组相同的数字的相邻的特性
- int* returnSize,用来作为二维数组行数下标的指针
- int** returnColumnSizes,用来返回列数,需要malloc,在有和为零的情况下赋值为3
- 二维数组的列数一般用二级指针比较方便
6 两数相除
2020-5-2
给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。返回被除数 dividend 除以除数 divisor 得到的商。整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
示例 1:
输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = truncate(3.33333…) = truncate(3) = 3
示例 2:
输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = truncate(-2.33333…) = -2
提示:
- 被除数和除数均为 32 位有符号整数。
- 除数不为 0。
- 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [ − 2 31 , 2 31 − 1 ] [−2^{31}, 2^{31} − 1] [−231,231−1]。本题中,如果除法结果溢出,则返回 2 31 − 1 2^{31} − 1 231−1。
1 一种使除数加倍增加的递归算法
- 利用多次的减法可以达到除法的效果,但是一个一个减,运行结果会超时
- 越界问题需要特别注意,出现负数时需要取负, T m i n T_{min} Tmin取负越界,则用long定义
- T m i n T_{min} Tmin:INT_MIN
- T m a x T_{max} Tmax:INT_MAX
long div1111(long a, long b)
{
if(a<b) return 0;
long count = 1;
long tb = b; // 在后面的代码中不更新b
while((tb+tb)<=a)
{
count = count + count; // 最小解翻倍
tb = tb+tb; // 当前测试的值也翻倍
}
return count + div1111(a-tb,b);
}
int divide(int dividend, int divisor)
{
long D=dividend;
long d=divisor;
int sign=1;
if(D == 0) return 0;
if(d == 1) return dividend;
if(d == -1)
{
if(D>INT_MIN) return -D;// 只要不是最小的那个整数,都是直接返回相反数就好啦
return INT_MAX;// 是最小的那个,返回最大的整数
}
if((D>0&&d<0) || (D<0&&d>0))
{
sign = -1;
}
if(D<0)D=-D;
if(d<0)d=-d;
long res = div1111(D,d);
if(sign>0)return res>INT_MAX?INT_MAX:res;
return -res;
}
2 使用移位
左移为除2,从
2
31
2^{31}
231开始对指数
i
i
i递减,找到能使“被除数-除数
∗
2
i
*2^{i}
∗2i”小于除数的
i
i
i,
a
n
s
+
+
=
2
i
ans++=2^{i}
ans++=2i
右移为乘2,被除数=被除数-除数
∗
2
i
*2^{i}
∗2i,循环
特殊情况:
- 被除数为0,返回0
- 被除数是1,返回除数
- 被除数是-1,除数为 T m i n T_{min} Tmin,返回 T m a x T_{max} Tmax
- 被除数是-1,除数大于 T m i n T_{min} Tmin,返回-除数
- 被除数是 T m i n T_{min} Tmin,除数为 T m i n T_{min} Tmin,返回1
- 被除数不为 T m i n T_{min} Tmin,除数为 T m i n T_{min} Tmin,返回0
int divide(int dividend, int divisor)
{
int sign=1,res=0;
if((dividend>0&&divisor<0) || (dividend<0&&divisor>0))sign = -1;
if(dividend == 0) return 0;
if(divisor == 1) return dividend;
if(divisor == -1)
{
if(dividend>INT_MIN) return -dividend;
return INT_MAX;
}
if(dividend == INT_MIN && divisor == INT_MIN)return 1;
else if(divisor == INT_MIN)return 0;
if(dividend == INT_MIN) // 若被除数为INT_MIN,先减一次,在再进行运算
{
dividend += abs(divisor);
res++;
}
int D=abs(dividend);
int d=abs(divisor);
for(int i = 31; i >= 0; i--)
{
if((D >> i) >= d)
{
res += 1 << i;
D -= d << i;
}
}
if(sign>0)return res>INT_MAX?INT_MAX:res;
return -res;
}
回顾
- 如果是32位的机器,那么long和int都是4个字节,可以使用int64_t进行替代,这样代码的可移植性会更好
- (dividend ^ divisor) < 0;//用异或来计算是否符号相异
- 边界坑多
- 在定义div函数时报错error: conflicting types for ‘div’ 1 ,大概是函数名重复
7 合并两个有序列表
2020-5-2
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
1 暴力迭代
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
struct ListNode *temp=(struct ListNode*) calloc(1, sizeof(struct ListNode));
struct ListNode *L=temp;
struct ListNode *a=l1;
struct ListNode *b=l2;
if(l1==NULL)return l2;
if(l2==NULL)return l1;
while(a&&b)
{
if(a->val<b->val)
{
temp->next=a;
a=a->next;
temp=temp->next;
}
else
{
temp->next=b;
b=b->next;
temp=temp->next;
}
}
if(a)temp->next=a;
if(b)temp->next=b;
return L->next;
}
-
时间复杂度: O ( n + m ) O(n + m) O(n+m) ,其中 n n n和 m m m 分别为两个链表的长度。因为每次循环迭代中,l1 和 l2 只有一个元素会被放进合并链表中, 因此 while 循环的次数不会超过两个链表的长度之和。所有其他操作的时间复杂度都是常数级别的,因此总的时间复杂度为 O ( n + m ) O(n+m) O(n+m)。
-
空间复杂度: O ( 1 ) O(1) O(1) 。我们只需要常数的空间存放若干变量。
2 递归
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{
if(l1==NULL)return l2;
if(l2==NULL)return l1;
if(l1->val < l2->val)
{
l1->next = mergeTwoLists(l1->next, l2);
return l1;
}
else
{
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
-
时间复杂度: O ( n + m ) O(n + m) O(n+m),其中 n和 m 分别为两个链表的长度。因为每次调用递归都会去掉 l1 或者 l2 的头节点(直到至少有一个链表为空),函数 mergeTwoList 至多只会递归调用每个节点一次。因此,时间复杂度取决于合并后的链表长度,即 O ( n + m ) O(n+m) O(n+m)。
-
空间复杂度: O ( n + m ) O(n + m) O(n+m),其中 n 和 m 分别为两个链表的长度。递归调用 mergeTwoLists 函数时需要消耗栈空间,栈空间的大小取决于递归调用的深度。结束递归调用时 mergeTwoLists 函数最多调用 n + m n+m n+m 次,因此空间复杂度为 O ( n + m ) O(n+m) O(n+m)。
8 无重复字符的最长子串
2020-5-3
给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
滑动窗口 无hash版本
int lengthOfLongestSubstring(char * s){
int n=strlen(s);
int max=0,l=0,h=0,length;
while(l<=h&&h<n)
{
for(int i=l;i<=h;i++)
{
if(*(s+i)==*(s+h+1))
{
length=h-l+1;
l=i+1; //将起始点刚好不包括重复的字符
if(length>max)max=length;
}
}
h++;
if(h==n)
{
length=h-l;
if(length>max)max=length;
}
}
return max;
}
回顾
- 哈希集合
- 子串->滑动窗口
- 时间复杂度 存疑
9 二叉树的中序遍历
2020-5-4
1 递归
- returnSize是返回向量的角标
- 返回向量需要用calloc分配
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
//Note: The returned array must be malloced, assume caller calls free().
#define MAXSIZE 1000
void inorder (struct TreeNode* root, int *returnSize, int *returnArray){
if (root->left == NULL && root->right == NULL)
{
returnArray[*returnSize] = root->val;
(*returnSize)++;
return;
}
if (root->left != NULL){
inorder(root->left, returnSize, returnArray);
}
returnArray[*returnSize] = root->val;
(*returnSize)++;
if (root->right != NULL)
{
inorder(root->right, returnSize, returnArray);
}
}
int* inorderTraversal(struct TreeNode* root, int* returnSize){
*returnSize = 0;
int *returnArray = (int*)calloc(MAXSIZE, sizeof(int));
if (root == NULL){return NULL;}
inorder(root, returnSize, returnArray);
return returnArray;
}
2用栈迭代
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
//Note: The returned array must be malloced, assume caller calls free().
#define MaxSize 1000
typedef struct stack {
struct TreeNode* data[MaxSize];
int top;
}Stack;
Stack S;
void initilize() {
S.top = 0;
}
void push(struct TreeNode* root) {
S.top=S.top+1;
S.data[S.top] = root;
}
struct TreeNode* pop() {
S.top=S.top-1;
return S.data[S.top+1];
}
int empty() {
return S.top==0;
}
int* inorderTraversal(struct TreeNode* root, int* returnSize){
*returnSize = 0;
struct TreeNode* current=root;
int *returnArray = (int*)calloc(MaxSize,sizeof(int));
if (root == NULL){return NULL;}
while(current!=NULL||!empty())
{
if(current!=NULL)
{
push(current);
current=current->left;
}
else
{
current=pop();
returnArray[*returnSize] = current->val;
(*returnSize)++;
current=current->right;
}
}
return returnArray;
}
10 最大子序和
2020-5-4
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
1 究极暴力版本
#define MAXSUM -2147483647
int maxSubArray(int* nums, int numsSize){
int low,high,sum;
int maxSum=MAXSUM;
for(int i=0;i<numsSize;i++)
{
sum=0;
for(int j=i;j<numsSize;j++)
{
sum=sum+nums[j];
if(sum>maxSum)
{
maxSum=sum;
low=i;
high=j;
}
}
}
return maxSum;
}
2 贪心算法
若已知 A [ 1 , . . . , j ] A[1,...,j] A[1,...,j]的最大子数组的情况下,则 A [ 1 , . . . , j + 1 ] A[1,...,j+1] A[1,...,j+1]的最大子数组,要么是 A [ 1 , . . . , j ] A[1,...,j] A[1,...,j]的最大子数组,要么是某个子数组 A [ i , . . . , j + 1 ] ( 1 ⩽ i ⩽ j + 1 ) A[i,...,j+1](1\leqslant i \leqslant j+1) A[i,...,j+1](1⩽i⩽j+1),在已知 A [ 1 , . . . , j ] A[1,...,j] A[1,...,j]的最大子数组的情况下,可以在线性时间内找出形如 A [ i , . . . , j + 1 ] A[i,...,j+1] A[i,...,j+1]的最大子数组
若当前指针所指元素之前的和小于零,则丢弃当前元素之前的数列
#define MAXSUM -2147483647
int maxSubArray(int* nums, int numsSize){
int low,high,sum=MAXSUM,maxSum=MAXSUM,currentHigh,currentLow;
for(int i=0;i<numsSize;i++)
{
currentHigh=i;
if(sum>0)sum=sum+nums[i];
else{
currentLow=i;
sum=nums[i];
}
if(sum>maxSum){
maxSum=sum;
low=currentLow;
high=currentHigh;
}
}
return maxSum;
}
3 动态规划
假设 nums 数组的长度是
n
n
n,下标从
0
0
0到
n
−
1
n−1
n−1。
我们用
a
i
a_i
ai代表 nums[i],用
f
(
i
)
f(i)
f(i)代表以第 i个数结尾的「连续子数组的最大和」,那么很显然我们要求的答案就是:
max 0 ≤ i ≤ n − 1 { f ( i ) } \max_{0 \leq i \leq n - 1} \{ f(i) \} 0≤i≤n−1max{f(i)}
因此我们只需要求出每个位置的
f
(
i
)
f(i)
f(i),然后返回
f
f
f数组中的最大值即可。
我们可以考虑
a
i
a_i
ai单独成为一段还是加入
f
(
i
−
1
)
f(i - 1)
f(i−1) 对应的那一段,这取决于
a
i
a_i
ai 和
f
(
i
−
1
)
+
a
i
f(i - 1) + a_i
f(i−1)+ai的大小,我们希望获得一个比较大的,于是可以写出这样的动态规划转移方程:
f
(
i
)
=
max
{
f
(
i
−
1
)
+
a
i
,
a
i
}
f(i) = \max \{ f(i - 1) + a_i, a_i \}
f(i)=max{f(i−1)+ai,ai}
若前一个元素大于零,则将其加到当前元素上
int max(int a,int b)
{
if(a>b)return a;
else return b;
}
int maxSubArray(int* nums, int numsSize){
int pre = 0, maxAns = nums[0];
for (int i=0;i<numsSize;i++) {
pre = max(pre + nums[i], nums[i]);
maxAns = max(maxAns, pre);
}
return maxAns;
}
4 分治
int maxCrossingSubarray(int* nums,int low,int mid,int high)
{
int left_sum=-32768;
int sum=0,max_left;
for(int i=mid;i>=low;i--)
{
sum=sum+nums[i];
if(sum>left_sum)
{
left_sum=sum;
max_left=i;
}
}
int right_sum=-32768;
int max_right;
sum=0;
for(int j=mid+1;j<=high;j++)
{
sum=sum+nums[j];
if(sum>right_sum)
{
right_sum=sum;
max_right=j;
}
}
return left_sum+right_sum;
}
int max(int* nums,int low,int high)
{
int left_sum,right_sum,cross_sum,mid;
if(low==high)return nums[low];
else
{
mid=(low+high)/2;
left_sum=max(nums,low,mid);
right_sum=max(nums,mid+1,high);
cross_sum=maxCrossingSubarray(nums,low,mid,high);
if(left_sum>=right_sum&&left_sum>=cross_sum)return left_sum;
else if(right_sum>=left_sum&&right_sum>=cross_sum)return right_sum;
else return cross_sum;
}
}
int maxSubArray(int* nums, int numsSize){
return max(nums,0,numsSize-1);
}
11 跳跃游戏 II
2020-5-4
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明:假设你总是可以到达数组的最后一个位置。
贪心
选择可达范围内的点下一步可达最远的位置
int jump(int* nums, int numsSize){
int count=0,i=0;
while(i<numsSize-1)
{
count++;
int max=0,Max=0;
if(nums[i]+i>=numsSize-1)return count;
for(int j=0;j<=nums[i]&&i+j<numsSize;j++)
{
if(j+nums[i+j]>=Max)
{
max=j;
Max=j+nums[i+j];
}
}
i=i+max;
}
return count;
}
12 验证二叉搜索树
2020-5-5
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
1 递归
设置上界与下界,左子树小于上界,右子树大于下界
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
bool helper(struct TreeNode*root,long lower,long upper){
if(root==NULL)return true;
if(root->val<=lower||root->val>=upper)return false;
return helper(root->left,lower,root->val)&&helper(root->right,root->val,upper);
}
bool isValidBST(struct TreeNode* root){
return helper(root,LONG_MIN,LONG_MAX);
}
2 中序遍历
二叉搜索树中序遍历的结果是一个递增数列,则在遍历过程中储存上一个出栈的值,若当前出栈的值小于等于上一个出栈的值,则返回false
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
//Note: The returned array must be malloced, assume caller calls free().
#define MaxSize 1000
typedef struct stack {
struct TreeNode* data[MaxSize];
int top;
}Stack;
Stack S;
void initilize() {
S.top = 0;
}
void push(struct TreeNode* root) {
S.top=S.top+1;
S.data[S.top] = root;
}
struct TreeNode* pop() {
S.top=S.top-1;
return S.data[S.top+1];
}
int empty() {
return S.top==0;
}
bool isValidBST(struct TreeNode* root){
long K=LONG_MIN;
struct TreeNode* current=root;
if (root == NULL){return true;}
while(current!=NULL||!empty())
{
if(current!=NULL)
{
push(current);
current=current->left;
}
else
{
current=pop();
if(current->val<=K)return false;
K=current->val;
current=current->right;
}
}
return true;
}
特殊测试用例
[2147483647]
[]
13 最低票价
2020-5-6
在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。
火车票有三种不同的销售方式:
一张为期一天的通行证售价为 costs[0] 美元;
一张为期七天的通行证售价为 costs[1] 美元;
一张为期三十天的通行证售价为 costs[2] 美元。
通行证允许数天无限制的旅行。 例如,如果我们在第 2 天获得一张为期 7 天的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。
返回你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费。
示例 1:
输入:days = [1,4,6,7,8,20], costs = [2,7,15]
输出:11
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, …, 9 天生效。在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。你总共花了 $11,并完成了你计划的每一天旅行。
示例 2:
输入:days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]
输出:17
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[2] = $15 买了一张为期 30 天的通行证,它将在第 1, 2, …, 30 天生效。在第 31 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 31 天生效。 你总共花了 $17,并完成了你计划的每一天旅行。
提示:
1 <= days.length <= 365
1 <= days[i] <= 365
days 按顺序严格递增
costs.length == 3
1 <= costs[i] <= 1000
动态规划,遍历365天
第
i
i
i天没有出行计划,即days数组中不含
i
i
i
d
p
(
i
)
=
d
p
(
i
+
1
)
dp(i)=dp(i+1)
dp(i)=dp(i+1)
否则
d
p
(
i
)
=
m
i
n
{
c
o
s
t
(
j
)
+
d
p
(
i
+
j
)
}
,
j
∈
1
,
7
,
30
dp(i)=min\{ cost(j)+dp(i+j)\},j∈{1,7,30}
dp(i)=min{cost(j)+dp(i+j)},j∈1,7,30
非递归版本
int Min(int a,int b,int c){
int min=a;
if(b<min)min=b;
if(c<min)min=c;
return min;
}
int Contain(int *A,int length,int a)
{
for(int i=0;i<length;i++)
{
if(a==A[i])
return 1;
}
return 0;
}
int mincostTickets(int* days, int daysSize, int* costs, int costsSize){
int dp_cost[400]={0}; //起码要有365+30 不然会溢出
for(int k=365;k>0;k--)
{
int J=Contain(days,daysSize,k);
if(J)
{
dp_cost[k]=Min(dp_cost[k+1]+costs[0],dp_cost[k+7]+costs[1],dp_cost[k+30]+costs[2]);
}
else
dp_cost[k]=dp_cost[k+1];
}
return dp_cost[1];
}
上述代码中使用一个Contain函数判断数组中是否存在k,构成两层嵌套,时间复杂度高,改成遍历过程中进行判断:
int Min(int a,int b,int c){
int min=a;
if(b<min)min=b;
if(c<min)min=c;
return min;
}
int mincostTickets(int* days, int daysSize, int* costs, int costsSize){
int dp_cost[400]={0};
int i=daysSize-1;
for(int k=365;k>0;k--)
{
if(i>=0&&days[i]==k)
{
dp_cost[k]=Min(dp_cost[k+1]+costs[0],dp_cost[k+7]+costs[1],dp_cost[k+30]+costs[2]);
i--;
}
else
dp_cost[k]=dp_cost[k+1];
}
return dp_cost[1];
}
再进一步,将自己写的min函数替换为math.h中的fmin函数,时间复杂度反而增加了qaq
int mincostTickets(int* days, int daysSize, int* costs, int costsSize){
int dp_cost[400]={0};
int i=daysSize-1;
for(int k=365;k>0;k--)
{
if(i>=0&&days[i]==k)
{
dp_cost[k]=fmin(fmin(dp_cost[k+1]+costs[0],dp_cost[k+7]+costs[1]),dp_cost[k+30]+costs[2]);
i--;
}
else
dp_cost[k]=dp_cost[k+1];
}
return dp_cost[1];
}
跳过这些不需要出行的日期的算法
假设当前在第i个元素,则分别计算满足 d a y [ i ] + 1 ≤ d a y [ j ] ; d a y [ i ] + 7 ≤ d a y [ j ] ; d a y [ i ] + 30 ≤ d a y [ j ] day[i]+1\leq day[j] ;day[i]+7\leq day[j];day[i]+30\leq day[j] day[i]+1≤day[j];day[i]+7≤day[j];day[i]+30≤day[j]的最小元素 j j j,比较三个中情况下的cost取最小的作为下一步的状态
似懂非懂,没事儿多看看
https://leetcode-cn.com/problems/minimum-cost-for-tickets/solution/zui-di-piao-jie-by-leetcode-solution/
回顾
- 根据转移方程可得到如下的递归过程,这怎么在C代码中实现啊 存疑
int dp(int k)
{
if(k>365)return 0;
int J=Contain(days,daysSize,k);
if(J)
{
dp_cost(k)=Min(dp(k+1)+costs[0],dp(k+7)+costs[1],dp(k+30)+costs[2]);
}
else
dp(k)=dp(k+1);
}
- 用min(0, i+1) min(0, i+7) min(0, i+30)来防止越界
14 另一个树的子树
2020-5-7
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
暴力匹配
- 使用递归写一个函数判断两树是否相等
- 遍历s中的每个节点,其对应的子树一一与t比较
- 时间复杂度为 O ( ∣ s ∣ × ∣ t ∣ ) O(∣s∣×∣t∣) O(∣s∣×∣t∣)
使用层序遍历的非迭代算法,定义MaxSize 10000不够用
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
#define MaxSize 100000
typedef struct queue {
struct TreeNode* data[MaxSize];
int front;
int rear;
}Queue;
Queue Q;
void initilize() {
Q.front = 0;
Q.rear = 0;
}
void Push(struct TreeNode* root) {
Q.data[++Q.rear] = root;
}
struct TreeNode* Pop() {
return Q.data[++Q.front];
}
int empty() {
return Q.rear == Q.front;
}
bool compare(struct TreeNode* a, struct TreeNode* b)
{
if(a==NULL&&b==NULL)return true;
else if(a==NULL||b==NULL)return false;
else if(a->val!=b->val)return false;
else{
return compare(a->left,b->left)&&compare(a->right,b->right);
}
}
bool isSubtree(struct TreeNode* s, struct TreeNode* t){
if (s == NULL&&t!=NULL) return false;
if (s == NULL&&t==NULL) return true;
bool T=false;
struct TreeNode *temp= (struct TreeNode*) calloc (1, sizeof(struct TreeNode));
Push(s);
while (!empty()) {
temp = Pop();
bool T=compare(temp,t);
if(T) return T;
if (temp->left)
Push(temp->left);
if (temp->right)
Push(temp->right);
}
return T;
}
一个更简洁的比较函数与递归遍历
bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
return (p == q) || (p != NULL && q != NULL && p->val == q->val && isSameTree(p->left, q->left) && isSameTree(p->right, q->right));
}
bool isSubtree(struct TreeNode* s, struct TreeNode* t){
if (s == NULL&&t!=NULL) return false;
if (s == NULL&&t==NULL) return true;
else if (s->val == t->val && isSameTree(s, t)) return true;
else return isSubtree(s->left, t) || isSubtree(s->right, t);
}
没有使用队列,内存使用少了一些
回顾
- 下限很低,上限很高,IG式题目
- 加空节点后遍历树,做字符串匹配,使用KMP匹配,时间复杂度为 O ( ∣ s ∣ + ∣ t ∣ ) O(∣s∣+∣t∣) O(∣s∣+∣t∣)
- 官方题解中树哈希的算法,打扰了
15 最大正方形
2020-5-8
在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。
示例:
输入:
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
输出: 4
1 暴力穷举
分别检查数组中每一个元素,在元素为1的基础上判断加上一行一列后元素的值是否均为1
时间复杂度:
O
(
m
n
min
(
m
,
n
)
2
)
O(mn \min(m,n)^2)
O(mnmin(m,n)2)
int maximalSquare(char** matrix, int matrixSize, int* matrixColSize){
int maxlength=0,flag,maxSide=0;
for(int i=0;i<matrixSize;i++)
{
for(int j=0;j<matrixColSize[i];j++)
{
maxSide=fmin(matrixSize-i,matrixColSize[i]-j);
if(matrix[i][j]=='1')
{
maxlength=fmax(maxlength,1);
flag=1;
for(int k=1;k<maxSide;k++)
{
if(matrix[i+k][j+k]=='0')break;
for(int s=0;s<k;s++)
{
if(matrix[i+s][j+k]=='0'||matrix[i+k][j+s]=='0')
{
flag=0;
break;
}
}
if(flag==1)maxlength=fmax(maxlength,k+1);
else break;
}
}
}
}
return maxlength*maxlength;
}
2 动态规划
用 d p ( i , j ) dp(i, j) dp(i,j) 表示以 ( i , j ) (i,j) (i,j) 为右下角,且只包含 1 的正方形的边长最大值
- 如果该位置的值是 0,则 d p ( i , j ) = 0 dp(i, j) = 0 dp(i,j)=0
- 如果该位置的值是 1,则 d p ( i , j ) dp(i, j) dp(i,j)的值由其上方、左方和左上方的三个相邻位置的 dp 值决定。具体而言,当前位置的元素值等于三个相邻位置的元素中的最小值加 1,状态转移方程如下:
d p ( i , j ) = m i n ( d p ( i − 1 , j ) , d p ( i − 1 , j − 1 ) , d p ( i , j − 1 ) ) + 1 dp(i, j)=min(dp(i−1, j), dp(i−1, j−1), dp(i, j−1))+1 dp(i,j)=min(dp(i−1,j),dp(i−1,j−1),dp(i,j−1))+1
- 时间复杂度: O ( m n ) O(mn) O(mn)
int maximalSquare(char** matrix, int matrixSize, int* matrixColSize){
int maxlength=0;
int **dp= (int**)malloc(sizeof(int*)*matrixSize);
for(int i = 0; i < matrixSize; i++){
dp[i] = (int*)malloc(sizeof(int)* matrixColSize[0]);
}
for(int i=0;i<matrixSize;i++)
{
for(int j=0;j<matrixColSize[i];j++)
{
if(matrix[i][j]=='0')dp[i][j]=0;
else if(matrix[i][j]=='1'&&(i==0||j==0))dp[i][j]=1;
else{
dp[i][j]=fmin(fmin(dp[(int)fmax(i-1,0)][j],dp[(int)fmax(i-1,0)][(int)fmax(j-1,0)]),dp[i][(int)fmax(j-1,0)])+1;
}
if(dp[i][j]>maxlength)maxlength=dp[i][j];
}
}
return maxlength*maxlength;
}
使用一维数组
妙啊
int maximalSquare(char** matrix, int matrixSize, int* matrixColSize){
int maxlength=0,cross=0;
if(matrixSize==0)return 0;
int *dp= (int*)malloc(sizeof(int)* matrixColSize[0]);
for(int i=0;i<matrixSize;i++)
{
for(int j=0;j<matrixColSize[0];j++)
{
int temp = dp[j];
if(matrix[i][j]=='0')dp[j]=0;
else if(matrix[i][j]=='1'&&(i==0||j==0))dp[j]=1;
else{
dp[j]=fmin(fmin(dp[j],dp[(int)fmax(j-1,0)]),cross)+1;
}
printf("%d ",dp[j]);
if(dp[j]>maxlength)maxlength=dp[j];
cross=temp;
}
}
return maxlength*maxlength;
}
回顾
- LeetCode的结果让我一脑袋问号,时间忽大忽小
- 动态申请二维数组
https://blog.csdn.net/fengxinlinux/article/details/51541003
函数的变量中matrixSize为行数,matrixCloSize是一维指针,长度为matrixSize,每个元素的值都为列数,即 m a t r i x C o l S i z e [ 0 ] = m a t r i x C o l S i z e [ 1 ] = . . . = m a t r i x C o l S i z e [ m a t r i x S i z e ] matrixColSize[0]=matrixColSize[1]=...=matrixColSize[matrixSize] matrixColSize[0]=matrixColSize[1]=...=matrixColSize[matrixSize]
以下为用二级指针动态申请二维数组 :
int **dp= (int**)malloc(sizeof(int*)*matrixSize);
for(int i = 0; i < matrixSize; i++){
dp[i] = (int*)malloc(sizeof(int)* matrixColSize[0]);
}
16 x的平方根
2020-5-9
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
1 暴力穷举
2 31 = 2147483648 2^{31}=2147483648 231=2147483648
2147483647 = 46340.95 \sqrt{2147483647}=46340.95 2147483647=46340.95
46341 × 46341 = 2147488281 46341\times46341=2147488281 46341×46341=2147488281
46340 × 46340 = 3147395600 46340\times46340=3147395600 46340×46340=3147395600
int mySqrt(int x){
if(x>=2147395600&&x<=2147483647)return 46340;
for(int i=1;i<=x;i++)
{
if(i*i==x)return i;
else if(i*i>x)return i-1;
}
return 0;
}
2 二分查找
时间复杂度 O ( l g n ) O(lgn) O(lgn)
int mySqrt(int x){
int l=0,r=x,mid,ans=0;
while(l<=r)
{
mid=(l+r)/2;
if((long long)mid*mid<=x)
{
ans=mid;
l=mid+1;
}
else r=mid-1;
}
return ans;
}
3 牛顿迭代
设 r r r是 f ( x ) = 0 f(x)=0 f(x)=0的根,选取 x 0 x_0 x0作为 r r r的近似初始值,过点 ( x 0 , f ( x 0 ) ) (x_0,f(x_0)) (x0,f(x0))做曲线 y = f ( x ) y=f(x) y=f(x)的切线 L : y = f ( x 0 ) + f ′ ( x 0 ) ( x − x 0 ) L:y=f(x_0)+f'(x_0)(x-x_0) L:y=f(x0)+f′(x0)(x−x0),则 L L L与 x x x轴交点的横坐标 x 1 = x 0 − f ( x 0 ) f ′ ( x 0 ) x_1=x_0-\frac{f(x_0)}{f'(x_0)} x1=x0−f′(x0)f(x0)为 r r r的一次近似值
迭代公式 x n + 1 = x n − f ( x n ) f ′ ( x n ) x_{n+1}=x_n-\frac{f(x_n)}{f'(x_n)} xn+1=xn−f′(xn)f(xn)
- 用浮点数得到结果,直接取整
- 一般来说,可以判断相邻两次迭代的结果的差值是否小于一个极小的非负数 ϵ \epsilon ϵ,其中 ϵ \epsilon ϵ 一般可以取 1 0 − 6 10^{−6} 10−6或 1 0 − 7 10^{-7} 10−7
- 时间复杂度 O ( l g n ) O(lgn) O(lgn)
int mySqrt(int x){
double x1=x,x2,error=1;
if(x==0)return 0; //零不能做除数
while(error>1e-7)
{
x2=0.5*x1+x/(2*x1);
error=fabs(x2-x1);
x1=x2;
}
return (int)x1;
}
17 二叉树的最近公共祖先
2020-5-10
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。
超时!超时!超时!
写Contain函数判断节点是否在树中,层序遍历树,返回最后一个满足条件的节点
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
#define MaxSize 50000
typedef struct queue {
struct TreeNode* data[MaxSize];
int front;
int rear;
}Queue;
Queue Q;
void initilize() {
Q.front = 0;
Q.rear = 0;
}
void Push(struct TreeNode* root) {
Q.data[++Q.rear] = root;
}
struct TreeNode* Pop() {
return Q.data[++Q.front];
}
int empty() {
return Q.rear == Q.front;
}
int Contain(struct TreeNode* root, struct TreeNode* p){
if(root==NULL)return 0;
if(root->val==p->val)return 1;
else return Contain(root->left,p)||Contain(root->right,p);
}
struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q){
struct TreeNode* lowest= (struct TreeNode*) calloc (1, sizeof(struct TreeNode));
struct TreeNode* temp= (struct TreeNode*) calloc (1, sizeof(struct TreeNode));
Push(root);
while (!empty()) {
temp = Pop();
if(Contain(temp,p)&&Contain(temp,q))lowest=temp;
//printf("%d ",lowest->val);
if (temp->left)
Push(temp->left);
if (temp->right)
Push(temp->right);
}
return lowest;
}
递归
- 树为空,返回 N U L L NULL NULL
- 树等于 p p p或等于 q q q,返回 r o o t root root
- 定义节点 l e f t left left表示当前节点的左子树包含 p p p或 q q q, r i g h t right right同理
- 如果 l e f t left left和 r i g h t right right同时不为空,则当前节点的左右子树分别包含 p p p与 q q q,当前节点为所求
- 若 l e f t left left为空, r i g h t right right不为空,则所求在当前节点的右子树,即返回节点 r i g h t right right,可分为两种情况:
- p , q p,q p,q 其中一个在 r o o t root root的右子树中,此时 r i g h t right right指向 p p p(假设为 p p p );
-
p
,
q
p,q
p,q两节点都在
r
o
o
t
root
root的右子树中,此时的
r
i
g
h
t
right
right指向最近公共祖先节点 ;
若查询的两个节点为15和20,在检查节点1的时候, l e f t left left为空, r i g h t right right不为空
6. 后序遍历,由下至上,最先满足条件的就是最深的
struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q){
if(root == NULL) return NULL;
if(root == p || root == q) return root;
struct TreeNode* left = lowestCommonAncestor(root->left, p, q);
struct TreeNode* right = lowestCommonAncestor(root->right, p, q);
if(left == NULL)return right;
if(right == NULL)return left;
if(left && right) return root;// p和q在两侧
return NULL; // 必须有返回值
}
18 pow(x, n)
2020-5-11
实现 pow(x, n) ,即计算 x 的 n 次幂函数。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
示例 2:
输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
说明:
-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。
我的我的
我觉得思想和官方题解方法二类似,但是好像米有掌握精髓,超时 存疑
double myPow(double x, int n){
if(n==0)return 1.0;
if(x==1)return 1.0;
double ans=x;
int N=n;
long i;
if(n<0)N=-n;
if(N==1)ans=x;
if(N>1)
{
for(i=2;i<=N;i=i*2)
{
ans=ans*ans;
}
for(int k=1;k<=N-i/2;k++)
{
ans=ans*x;
}
}
if(n>0)return ans;
else return 1/ans;
}
分治 递归
2 10 = 2 5 × 2 5 2^{10}=2^5\times2^5 210=25×25
double positive(double x,int n){
if(n==0)return 1;
double ans=positive(x,n/2);
if(n%2==1)return ans*ans*x;
else return ans*ans;
}
double myPow(double x, int n){
if(n==INT_MIN)return 1/(x*positive(x,INT_MAX));
if(n==0)return 1;
else if(n>0) return positive(x,n);
else return 1.0/positive(x,-n);
}
时间复杂度:
O
(
l
o
g
n
)
O(logn)
O(logn)
空间复杂度:
O
(
l
o
g
n
)
O(logn)
O(logn)
迭代
double positive(double x,int N){
double ans = 1.0;
double x_contribute = x;
while (N > 0) {
if (N % 2 == 1) ans *= x_contribute;
x_contribute *= x_contribute;
N /= 2;
}
return ans;
}
double myPow(double x, int n){
if(n==INT_MIN)return 1/(x*positive(x,INT_MAX));
if(n==0)return 1;
else if(n>0) return positive(x,n);
else return 1.0/positive(x,-n);
}
回顾
- 在越界的边缘疯狂试探 芝麻开门密码:2147483648
- 递归的返回值还需要再仔细琢磨
- 能想象到二进制是真的牛批了
19 只出现一次的数
2020-5-14
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素,线性时间复杂度不用额外空间
如果没有时间复杂度和空间复杂度的限制,这道题有很多种解法,可能的解法有如下几种:
- 使用集合存储数字。遍历数组中的每个数字,如果集合中没有该数字,则将该数字加入集合,如果集合中已经有该数字,则将该数字从集合中删除,最后剩下的数字就是只出现一次的数字。
- 使用哈希表存储每个数字和该数字出现的次数。遍历数组即可得到每个数字出现的次数,并更新哈希表,最后遍历哈希表,得到只出现一次的数字。
- 使用集合存储数组中出现的所有数字,并计算数组中的元素之和。由于集合保证元素无重复,因此计算集合中的所有元素之和的两倍,即为每个元素出现两次的情况下的元素之和。由于数组中只有一个元素出现一次,其余元素都出现两次,因此用集合中的元素之和的两倍减去数组中的元素之和,剩下的数就是数组中只出现一次的数字。
上述三种解法都需要额外使用 O ( n ) O(n) O(n) 的空间
位运算
异或运算有以下三个性质:
- 任何数和0做异或运算,结果仍然是原来的数,即 a ⊕ 0 = a a⊕0=a a⊕0=a
- 任何数和其自身做异或运算,结果是 0,即 a ⊕ a = 0 a⊕a=0 a⊕a=0
- 异或运算满足交换律和结合律,即 a ⊕ b ⊕ a = b ⊕ a ⊕ a = b ⊕ ( a ⊕ a ) = b ⊕ 0 = b a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b
int singleNumber(int* nums, int numsSize){
int k = 0;
for (int i=0;i<numsSize;i++) k^=nums[i];
return k;
}
回顾
我琢磨了一个多小时,从和减去每一个数判断奇偶性出发,各种分类讨论,未果
计算机思维匮乏,怎么能硬往数学想呢
20 和为K的子数组
2020-5-15
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
说明 :数组的长度为 [1, 20,000],数组中元素的范围是 [-1000, 1000] ,整数 k 的范围是 [-1e7, 1e7]。
穷举
有负数,不能大于k时跳出,有零,不能等于k时跳出
int subarraySum(int* nums, int numsSize, int k){
int count=0;
for(int i=0;i<numsSize;i++)
{
int sum=0;
for(int s=i;s<numsSize;s++)
{
sum=sum+nums[s];
if(sum==k)count++;
}
}
return count;
}
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
21 K 个一组翻转链表
2020-5-16
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:
给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
说明:
你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
翻转链表
设置两个节点pre和next,pre初值设为NULL,next节点指向当前指针的next节点,从表头开始,逐次将当前节点的next指针指向pre,当前节点更新为新的next,当当前节点为空是退出,最后返回pre
struct ListNode* reverseGroup(struct ListNode* head){
struct ListNode* pre=NULL;
struct ListNode* curr=head;
while(curr!=NULL)
{
struct ListNode* next=curr->next;
curr->next=pre;
pre=curr;
curr=next;
}
return pre;
}
分组翻转并连接
- 设置哨兵节点指向链表的头节点
- 设置pre节点,指向当前组第一个节点,pre->next为翻转后的链表
- 设置end节点,是当前组的最后一个节点
- 设置start节点,为当前组的头结点
- 设置next节点,为当前组最后一个节点next节点,为组翻转后next指针指向的节点
- pre初始值为哨兵节点,每组的pre节点是上一组的start节点
- 循环k次得到每一组的end节点
- 当不足k个节点时,直接退出循环
struct ListNode* reverseKGroup(struct ListNode* head, int k){
struct ListNode* dummy = (struct ListNode*)calloc(1, sizeof(struct ListNode));
dummy->next=head;
struct ListNode* pre = dummy;
struct ListNode* end = dummy;
while(end->next!=NULL)
{
for(int i=0;i<k&&end!=NULL;i++)end=end->next;
if(end==NULL)break;
struct ListNode* start=pre->next;
struct ListNode* next=end->next;
end->next=NULL;
pre->next=reverseGroup(start);
start->next=next;
pre=start;
end=pre;
}
return dummy->next;
}
回顾
需要加一些辅助的节点,想清楚,容易乱
https://leetcode-cn.com/problems/reverse-nodes-in-k-group/solution/tu-jie-kge-yi-zu-fan-zhuan-lian-biao-by-user7208t/
22 课程表2
2020-5-17
题目描述
现在你总共有 n 门课需要选,记为 0 到 n-1。在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
示例 :
输入: 4, [[1,0],[2,0],[3,1],[3,2]]
输出: [0,1,2,3] or [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3]
首先明确函数中各个参数的含义
int* findOrder(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize, int* returnSize)
- numCourses 课程总数
- prerequisites 二级指针表示二维数组,即A=[[1,0],[2,0],[3,1],[3,2]];prerequisites[0][0]=1 prerequisites[1][0]=2
- prerequisitesSize 二维数组的行数,即4
- prerequisitesColSize 一维数组,表示A中每行对应的列数,即[2,2,2,2]
- *returnSize返回数组的大小,初始设为零,再逐步加一,不满足条件时可直接置零输出空数组
广度优先搜索
- 统计各个节点的入度
- 将度为零的节点入队
- 出队,并将以出队元素为队首的节点的入度减一,若节点度为零,入队
- 若队为空之后仍有节点,返回空数组
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
#define MaxSize 10000
typedef struct queue{
int data[MaxSize];
int front;
int rear;
}Queue;
Queue Q;
void initilize()
{
Q.front=0;
Q.rear=0;
}
void QueueIn(int d)
{
Q.data[++Q.rear]=d;
}
int QueueOut()
{
return Q.data[++Q.front];
}
int Empty()
{
return Q.front==Q.rear;
}
int* findOrder(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize, int* returnSize){
//返回的数组及其大小
int *ans=(int*)calloc(numCourses+1,sizeof(int));
*returnSize=0;
//统计入度
int *indegrees=(int*)calloc(numCourses+1,sizeof(int));
for(int i=0;i<prerequisitesSize;i++)
{
int index=prerequisites[i][0];
indegrees[index]++;
}
//度为零 入队
for(int i=0;i<numCourses;i++)
{
if(indegrees[i]==0)QueueIn(i);
}
while(!Empty())
{
int temp=QueueOut();
numCourses--;
ans[*returnSize]=temp;
(*returnSize)++;
for(int i=0;i<prerequisitesSize;i++)
{
if(prerequisites[i][1]==temp)
{
indegrees[prerequisites[i][0]]--;
if(indegrees[prerequisites[i][0]]==0)QueueIn(prerequisites[i][0]);
}
}
}
if(numCourses!=0)*returnSize=0;
return ans;
}
深度优先搜索
- 访问到正在访问状态的节点,说明有环
- 将访问完成的节点一一入栈
- 搜索过程中三种状态:未被访问 正在访问 访问完成
#define MaxSize 10000
typedef struct stack{
int data[MaxSize];
int top;
}Stack;
Stack S;
void initilize()
{
S.top=0;
}
void push(int x)
{
S.top++;
S.data[S.top]=x;
}
int pop()
{
S.top--;
return S.data[S.top+1];
}
int Empty(){
return S.top==0;
}
int visited[MaxSize];
int ring=0;
void DFS(int u,int m,int **prerequisites)
{
visited[u]=1;
for(int i=0;i<m;i++)
{
if(prerequisites[i][1]==u) //prerequisites[i][0]是邻接点
{
if(visited[prerequisites[i][0]]==0)
{
DFS(prerequisites[i][0],m,prerequisites);
if (ring==1)return;
}
else if(visited[prerequisites[i][0]]==1) //有环
{
ring=1;
return;
}
}
}
visited[u]=2;
push(u);
}
int* findOrder(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize, int* returnSize){
int *ans=(int*)calloc(numCourses+1,sizeof(int));
*returnSize=0;
for(int i=0;i<numCourses;i++)
{
if(!visited[i])DFS(i,prerequisitesSize,prerequisites);
}
while(!Empty())
{
ans[*returnSize]=pop();
(*returnSize)++;
}
if(ring==1)(*returnSize)=0;
return ans;
}
同样的输入,测试结果是对的,提交运行就出错,一脑袋问号 存疑
23 乘积最大子数组
2020-5-18
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
动态规划
当之前的和为整数,当前组的值为整数时,需要保留负的结果,以便再次出现负值时负负得正
定义
d
p
M
a
x
[
i
]
=
m
a
x
{
d
p
M
a
x
[
i
−
1
]
∗
n
u
m
s
[
i
]
,
n
u
m
s
[
i
]
}
dpMax[i] = max\{dpMax[i-1] * nums[i],nums[i]\}
dpMax[i]=max{dpMax[i−1]∗nums[i],nums[i]}
d
p
M
i
n
[
i
]
=
m
i
n
{
d
p
M
i
n
[
i
−
1
]
∗
n
u
m
s
[
i
]
,
n
u
m
s
[
i
]
}
dpMin[i] = min\{dpMin[i-1] * nums[i],nums[i]\}
dpMin[i]=min{dpMin[i−1]∗nums[i],nums[i]}
分情况讨论:
- 当 n u m s [ i ] > = 0 nums[i] >= 0 nums[i]>=0 并且 d p M a x [ i − 1 ] > 0 dpMax[i-1] > 0 dpMax[i−1]>0, d p M a x [ i ] = d p M a x [ i − 1 ] ∗ n u m s [ i ] dpMax[i] = dpMax[i-1] * nums[i] dpMax[i]=dpMax[i−1]∗nums[i]
- 当 n u m s [ i ] > = 0 nums[i] >= 0 nums[i]>=0 并且 d p M a x [ i − 1 ] < = 0 dpMax[i-1] < =0 dpMax[i−1]<=0, d p M a x [ i ] = n u m s [ i ] dpMax[i] = nums[i] dpMax[i]=nums[i]
- 当 n u m s [ i ] < 0 nums[i] < 0 nums[i]<0,并且 d p M i n [ i − 1 ] < 0 dpMin[i-1] < 0 dpMin[i−1]<0, d p M a x [ i ] = d p M i n [ i − 1 ] ∗ n u m s [ i ] dpMax[i] = dpMin[i-1] * nums[i] dpMax[i]=dpMin[i−1]∗nums[i]
- 当 n u m s [ i ] < 0 nums[i] < 0 nums[i]<0,并且 d p M i n [ i − 1 ] > = 0 , d p M a x [ i ] = n u m s [ i ] dpMin[i-1] >= 0,dpMax[i] = nums[i] dpMin[i−1]>=0,dpMax[i]=nums[i]
因此 d p M a x [ i ] = m a x ( d p M a x [ i − 1 ] ∗ n u m s [ i ] , d p M i n [ i − 1 ] ∗ n u m s [ i ] , n u m s [ i ] ) dpMax[i] = max(dpMax[i-1] * nums[i], dpMin[i-1] * nums[i], nums[i]) dpMax[i]=max(dpMax[i−1]∗nums[i],dpMin[i−1]∗nums[i],nums[i])
int maxProduct(int* nums, int numsSize){
int dpMax = nums[0];
int dpMin = nums[0];
int ans = nums[0];
for (int i = 1; i < numsSize; i++) {
int preMax = dpMax;
dpMax = fmax(dpMin * nums[i], fmax(dpMax * nums[i], nums[i]));
dpMin = fmin(dpMin * nums[i], fmin(preMax * nums[i], nums[i]));
ans = fmax(ans, dpMax);
}
return ans;
}
当负数出现时则max与min进行交换再进行下一步计算
int max(int a,int b)
{
return a>b?a:b;
}
int min(int a,int b)
{
return a<b?a:b;
}
#define MAXSUM -2147483647
int maxProduct(int* nums, int numsSize){
int preMax=1,preMin=1,ans=MAXSUM;
for(int i=0;i<numsSize;i++)
{
if(nums[i]<0)
{
int temp=preMax;
preMax=preMin;
preMin=temp;
}
preMax=max(preMax*nums[i],nums[i]);
preMin=min(preMin*nums[i],nums[i]);
ans=max(ans,preMax);
}
return ans;
}
24 验证回文字符串2
2020-5-19
给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
示例 1:
输入: “aba”
输出: True
示例 2:
输入: “abca”
输出: True
解释: 你可以删除c字符。
注意:字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。
贪心
bool ttt(char *s,int l,int h)
{
for(int i=l,j=h;i<j;i++,j--)
{
if(s[i]!=s[j])return false;
}
return true;
}
bool validPalindrome(char * s){
int n=strlen(s),flag=0;
for(int i=0,j=n-1;i<j;i++,j--)
{
if(s[i]!=s[j]){
return ttt(s,i,j-1)||ttt(s,i+1,j);
}
}
return true;
}
好久没有独立AC且结果不错了,虽然是个简单题,但还是感动老马555555
25 每个元音包含偶数次的最长子字符串
2020-5-20
给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出现了偶数次。
示例 1:
输入:s = “eleetminicoworoep”
输出:13
解释:最长子字符串是 “leetminicowor” ,它包含 e,i,o 各 2 个,以及 0 个 a,u
提示:
- 1 < = s . l e n g t h < = 5 ∗ 1 0 5 1 <= s.length <= 5 *10^5 1<=s.length<=5∗105
- s s s 只包含小写英文字母。
前缀和+状态压缩
前缀和
在不重复遍历子串的前提下,快速求出这个区间里元音字母出现的次数,对于一个区间,我们可以用两个前缀和的差值,得到其中某个字母的出现次数。
定义
p
r
e
[
i
]
[
k
]
{pre}[i][k]
pre[i][k]表示在字符串前
i
i
i 个字符中,第
k
k
k个元音字母一共出现的次数.
位运算
将五个元音字母数目的奇偶性状态用01表示,并储存在一个5位的二进制数中,则会产生32中奇偶状态,对应数字0~31,当数字相同时表示状态相同,则两个状态所在位置之差为一个次优解。
状态更新:
0表示偶数个,1表示奇数个,当出现a时,用00001与当前的状态做异或运算,最后一位若为1则更新为零,若为0则更新为1,其他位不变。
int findTheLongestSubstring(char * s){
int n=strlen(s);
int status=0,ans=0;
int *pos=(int *)malloc(sizeof(int)*32);
pos[0]=0;
for(int i=1;i<32;i++) //不知道咋赋初值为-1
{
pos[i]=-1;
}
for(int i=0;i<n;i++){
if(s[i]=='a')status ^= (1 << 0);
else if(s[i]=='e')status ^= (1 << 1);
else if(s[i]=='i')status ^= (1 << 2);
else if(s[i]=='o')status ^= (1 << 3);
else if(s[i]=='u')status ^= (1 << 4);
if (pos[status]!=-1) {
ans = fmax(ans, i + 1 - pos[status]);
}
else {
pos[status] = i + 1;
}
}
return ans;
}
剪枝
int ans = 0;
int isOK(int *map){ //0,4,8,14,20分别对应5个元音字母
if (map[0] % 2 == 0 && map[4] % 2 == 0 &&
map[8] % 2 == 0 && map[14] % 2 == 0 &&
map[20] % 2 == 0) {
return 1;
} else {
return 0;
}
}
void left2right(int *map1, int *map2, char *s)
{
int sLen = strlen(s);
int left = 0; //左指针初始指向首位
int right = sLen - 1; //右指针初始指向末尾
while (left <= right) {
if ((right - left) < (ans - 1)) { //当左右指针间距小于当前答案时,剪枝
break;
}
while (left <= right) { //在一轮循环中,右指针固定,移动左指针
if (isOK(map1) == 1) { //如果满足条件就更新答案,并跳出本轮,接下来移动右指针进入下一轮
ans = fmax(ans, right - left + 1);
break;
} else { //左指针不断右移,把字母从统计表中删去
map1[s[left] - 'a']--;
left++;
}
if ((right - left) < (ans - 1)) { //当左右指针间距小于当前答案时,剪枝
break;
}
}
//此时一轮搜索结束,当前右指针所在位置的最长子串已经找到并更新到ans
//重置左指针指向开头,右指针左移一位;进入下一轮
left = 0;
map2[s[right] - 'a']--; //去掉right指针指向的字母,对应的数量减一
right--;
memcpy(map1, map2, sizeof(int)*26); //将2复制给1
}
return;
}
int findTheLongestSubstring(char * s){
int map[26] = {0};
int map1[26] = {0};
int map2[26] = {0};
int sLen = strlen(s);
//统计每个字母出现的次数
for (int i = 0; i < sLen; i++) {
map[s[i] - 'a']++;
}
ans = 0;
memcpy(map1, map, sizeof(int)*26);
memcpy(map2, map, sizeof(int)*26);
left2right(map1, map2, s);
return ans;
}
回顾
- 奇偶个数校验->位运算,异或
- 遇到有限的参数(小于20个)表状态, 想到二进制状态压缩 (bitmask)
- 遇到求最长的连续子串使得和为k想到 前缀和 加哈希表记录第一次出现某一状态的位置。
26 寻找重复数
2020-5-26
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
说明:
不能更改原数组(假设数组是只读的)。
只能使用额外的
O
(
1
)
O(1)
O(1) 的空间。
时间复杂度小于
O
(
n
2
)
O(n^2)
O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。
二分法
- 定义 c n t [ i ] cnt[i] cnt[i]表示数组中小于等于 i i i的数的个数
- 对于每个 c n t [ i ] cnt[i] cnt[i],可以在 O ( n ) O(n) O(n)时间内求得
- 对于遍历 i , i ∈ [ 1 , n ] i,i\in[1,n] i,i∈[1,n],使用二分法,如果出现 c n t [ i ] > i cnt[i]>i cnt[i]>i,则重复的数字一定小于等于当前数字
int findDuplicate(int* nums, int numsSize){
int left=1,right=numsSize-1,ans;
while(left<=right){
int mid=(left+right)/2;
int cnt=0;
for(int i=0;i<numsSize;i++){
if(nums[i]<=mid)cnt++;
}
if(cnt<=mid){
left=mid+1;
}
else{
right=mid-1;
ans=mid;
}
}
return ans;
}
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
空间复杂度:
O
(
1
)
O(1)
O(1)
快慢指针
int findDuplicate(int* nums, int numsSize){
int slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
27 和可被 K 整除的子数组
2020-5-27
给定一个整数数组 A,返回其中元素之和可被 K 整除的(连续、非空)子数组的数目。
示例:
输入:A = [4,5,0,-2,-3,1], K = 5
输出:7
解释:有 7 个子数组满足其元素之和可被 K = 5 整除:[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]
提示:
1 <= A.length <= 30000
-10000 <= A[i] <= 10000
2 <= K <= 10000
数据结构 哈希表
struct hashNode {
int key;
int value;
struct hashNode *next;//冲突时,用指针连接
};
struct hashMap {
int size;
struct hashNode *data;
};
struct hashMap * hashCreat(int size)
{
//新建一个哈希表
struct hashMap * hashMapInfo;
//为哈希表分配地址
hashMapInfo = (struct hashMap *)malloc(sizeof(struct hashMap));
//设置哈希表的大小
hashMapInfo->size = size;
//为哈希表的data部分分配地址
hashMapInfo->data = (struct hashNode *)malloc(sizeof(struct hashNode) * size);
//初始化哈希表每个哈希节点的对象
for (int i = 0; i < size; i++) {
hashMapInfo->data[i].value = 0;
hashMapInfo->data[i].key = INT_MIN;
hashMapInfo->data[i].next = NULL;
}
return hashMapInfo;
}
//将键为key的节点放入到哈希表中
void Put(int key, struct hashMap * hashMapInfo)
{
//计算节点的位置
int pos = abs(key) % hashMapInfo->size;
//定义哈希节点,其值为 key所在位置的数据的链表地址
struct hashNode *data = &hashMapInfo->data[pos];
//定义一个新的哈希节点,为空
struct hashNode *newNode = NULL;
if (data->key == INT_MIN) { //链表未占用
data->key = key;
data->value = 1;
return;
}
while (data != NULL) {//while循环要考虑当前节点,不能从data->next开始
if (data->key == key) { //当前链表已存在该key
data->value++;
return;
}
if (data->next == NULL) { //为后续添加Node做准备
break;
}
data = data->next; //链表next节点
}
/* 添加节点 */
newNode = (struct hashNode *)malloc(sizeof(struct hashNode));
newNode->key = key;
newNode->value = 1;
newNode->next = NULL;
data->next = newNode;
return;
}
//获得键为key的键值
int Get(int key, struct hashMap * hashMapInfo)
{
int count = 0;
int pos = abs(key) % hashMapInfo->size; //选择hash桶
struct hashNode *data = &hashMapInfo->data[pos];
while (data != NULL) { /*遍历链表,匹配key*/
if (data->key == key) {
count = data->value;
break;
}
data = data->next;
}
return count;
}
暴力穷举
int subarraysDivByK(int* A, int ASize, int K){
int count=0;
for(int i=0;i<ASize;i++){
int sum=0;
for(int j=i;j<ASize;j++){
sum+=A[j];
if(sum%K==0)count++;
}
}
return count;
}
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
前缀和+哈希
- 涉及到子数组的问题,使用前缀和来减少重复的求和计算
- 同余定理: ( p r e [ i ] − p r e [ j ] ) % K = = 0 ⇒ p r e [ i ] % K = = p r e [ j ] % K (pre[i]-pre[j])\%K==0 \Rightarrow pre[i]\%K==pre[j]\%K (pre[i]−pre[j])%K==0⇒pre[i]%K==pre[j]%K
- 类似于“ 和为K的子数组”,将 p r e [ i ] % K pre[i]\%K pre[i]%K作为键,出现的次数作为键值,遍历A依次计算 p r e [ i ] % K pre[i]\%K pre[i]%K,并将此key对应的哈希表的键值加到count中,并将key放到哈希表中,如果是第一次出现,自然为零,若是第二次出现,count=1;若是第三次出现,count=3,A1-A2、A1-A3、A2-A3
- 当被除数为负数时取模结果为负数,需要纠正
int subarraysDivByK(int* A, int ASize, int K){
struct hashMap *hashMapInfo;
int count=0,sum = 0;
hashMapInfo = hashCreat(ASize);
Put(0, hashMapInfo); //初始化为0,这个很重要,后续sum=k时需要
for (int i = 0; i <ASize; i++) {
sum+=A[i];
int key=(sum%K+K)%K;
count += Get(key, hashMapInfo);
Put(key, hashMapInfo);
}
return count;
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
m
i
n
(
n
,
K
)
)
O(min(n,K))
O(min(n,K))
在遍历结束后再遍历哈希表,用排列组合的方法来统计答案
int subarraysDivByK(int* A, int ASize, int K){
struct hashMap *hashMapInfo;
int count=0,sum = 0;
hashMapInfo = hashCreat(ASize);
Put(0, hashMapInfo); //初始化为0,这个很重要,后续sum=k时需要
for (int i = 0; i <ASize; i++) {
sum+=A[i];
int key=(sum%K+K)%K;
Put(key, hashMapInfo);
}
for(int key=0;key<K;key++)
{
int w=Get(key, hashMapInfo);
count+=w*(w-1)/2;
}
return count;
}
前缀和+数组
余数的范围可预期,用数组更直观
int subarraysDivByK(int* A, int ASize, int K){
int* res = (int*)malloc(sizeof(int)*K);
memset(res,0,K*sizeof(int));
res[0] = 1;
int cnt = 0,m = 0,sum = 0;
for(int i = 0 ; i < ASize ;i++){
sum+=A[i];
m = (sum%K + K)%K;
res[m]++;
}
for(int j = 0 ; j < K ;j++){
if(res[j] >= 2){
cnt+=res[j]*(res[j]-1)/2;
}
}
return cnt;
}
28 字符串解码
2020-5-28
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例:
s = “3[a]2[bc]”, 返回 “aaabcbc”.
s = “3[a2[c]]”, 返回 “accaccacc”.
s = “2[abc]3[cd]ef”, 返回 “abcabccdcdcdef”.
思路
- 不为右括号,入栈
- 否则,while (top(S)!=’[’) 出栈,存入数组
- 数组翻转
- 左括号出栈
- 获取左括号前的数字
- 重复存入数组
- 数字出栈
- 重复后的数组入栈
栈
#define MaxSize 10000
typedef struct stack {
char data[MaxSize];
int top;
}Stack;
void initilize(Stack *S) {
S->top = -1;
}
void push(char x,Stack *S) {
S->top=S->top+1;
S->data[S->top] = x;
}
void pop(Stack *S) {
S->top=S->top-1;
}
char top(Stack *S) {
return S->data[S->top];
}
int empty(Stack *S) {
return S->top==-1;
}
char* reverse(char * s)
{
for(int i = 0, j = strlen(s) - 1; i < j; i++, j--){
char tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
return s;
}
char * decodeString(char * s){
Stack * S = (Stack*)malloc(sizeof(Stack));
initilize(S);
int ansLength;
for(int i=0;i<strlen(s);i++)
{
//不是右括号就一直入栈
if(s[i]!=']'){
push(s[i],S);
}
else{
char* temp=(char *)malloc(MaxSize*sizeof(char));//临时数组,保存括号中间的字符
int k=0; //临时数组的大小
while (top(S)!='['){
temp[k++]=top(S);//栈顶元素存入临时数组
pop(S);//栈顶元素出栈
}
temp[k]='\0';//尾部添加\0转成字符串
temp=reverse(temp);
pop(S);//左括号出栈
int repeat=0;
int d=0; //处理一位以上的数字
while(!empty(S)&&top(S)>='0'&&top(S)<='9'){
repeat+=(top(S)-'0')*pow(10,d); //获取左括号前的数字
pop(S);//数字出栈
d++;
}
for(int i=0;i<repeat;i++){
for(int j=0;j<k;j++)
{
push(temp[j],S);
}
}
}
}
push('\0',S);
return S;
}
回顾
- 思路清晰,代码写不出来,555
- 栈的数据结构中,对应函数的形参需要加*,否则改变不了对应的值,同时变成指针之后要用箭头而不是点
- S->top = -1,这样第一个入栈的数据对应的top就是0
- 要考虑到两位及其以上的数字,使用指数和加法还原数字
- 尾部添加\0转成字符串
- 递归下次再看,下次一定
29 打家劫舍
2020-5-29
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。偷窃到的最高金额 = 2 + 9 + 1 = 12 。
动态规划
对于当前房屋i,有两种选择
- 偷 i i i,不偷 i − 1 i-1 i−1,此时的收益是当前房屋加 i − 2 i-2 i−2的收益
- 不偷,当前收益等于 i − 1 i-1 i−1的收益
状态转移方程为:
d
p
[
i
]
=
m
a
x
(
d
p
[
i
−
2
]
+
n
u
m
s
[
i
]
,
d
p
[
i
−
1
]
)
dp[i]=max(dp[i−2]+nums[i],dp[i−1])
dp[i]=max(dp[i−2]+nums[i],dp[i−1])
边界条件为:
d
p
[
0
]
=
n
u
m
s
[
0
]
dp[0]=nums[0]
dp[0]=nums[0],只有一间房屋,则偷窃该房屋
d
p
[
1
]
=
m
a
x
(
n
u
m
s
[
0
]
,
n
u
m
s
[
1
]
)
dp[1]=max(nums[0],nums[1])
dp[1]=max(nums[0],nums[1]),只有两间房屋,选择其中金额较高的房屋进行偷窃
int rob(int* nums, int numsSize){
int *dp= (int*)malloc(sizeof(int)*(numsSize+1));
if(numsSize==0)return 0;
if(numsSize==1){
return nums[0];
}
dp[0]=nums[0];
dp[1]=fmax(nums[0],nums[1]);
for(int i=2;i<numsSize;i++){
dp[i]=fmax((dp[i-2]+nums[i]),dp[i-1]);
}
return dp[numsSize-1];
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
加滚动数组
int rob(int* nums, int numsSize){
if(numsSize==0)return 0;
if(numsSize==1){
return nums[0];
}
int dp0=nums[0];
int dp1=fmax(nums[0],nums[1]);
for(int i=2;i<numsSize;i++){
int temp=dp1;
dp1=fmax((dp0+nums[i]),dp1);
dp0=temp;
}
return dp1;
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
回顾
动态规划,一看题就懵,一看答案就懂
30 柱状图中的最大矩形
2020-5-30
暴力超时
int largestRectangleArea(int* heights, int heightsSize){
int max=0;
for(int i=0;i<heightsSize;i++){
int h=heights[i];
int l=1;
for(int j=i-1;j>=0;j--){
if(heights[j]<h)break;
l++;
}
for(int k=i+1;k<heightsSize;k++){
if(heights[k]<h)break;
l++;
}
max=h*l>max?h*l:max;
}
return max;
}
单调栈
- 对于当前数 h e i g h t s [ i ] heights[i] heights[i],计算左右两侧小于 h e i g h t s [ i ] heights[i] heights[i]的柱子对应的角标,两角标之差减一即为当前高度对应的横轴的长度
- 分别从左到右、从右到左循环,计算左侧与右侧
- 栈空:对于左侧设为-1,对于右侧设为 h e i g h t s S i z e heightsSize heightsSize
- 栈顶元素的高度大于等于当前元素的高度,出栈
- 栈顶到栈底递减
#define MaxSize 30000
typedef struct Stack{
int top;
int num[MaxSize];
}Stack;
Stack S;
void init(){
S.top=0;
}
void push(int a){
S.top++;
S.num[S.top]=a;
}
int pop(){
if(S.top==0)return 0;
S.top--;
return S.num[S.top+1];
}
int Top(){
return S.num[S.top];
}
int empty(){
if(S.top==0)return 1;
else return 0;
}
int largestRectangleArea(int* heights, int heightsSize){
init();
int *left=(int *)malloc((heightsSize+1)*sizeof(int));
int *right=(int *)malloc((heightsSize+1)*sizeof(int));
for(int i=0;i<heightsSize;i++){
if(empty()){
left[i]=-1;
}else{
while(!empty()&&heights[Top()]>=heights[i]){
pop();
}
if(empty()){
left[i]=-1;
}else{
left[i]=Top();
}
}
push(i);
}
init();
for(int j=heightsSize-1;j>=0;j--){
if(empty()){
right[j]=heightsSize;
}else{
while(!empty()&&heights[Top()]>=heights[j]){
pop();
}
if(empty()){
right[j]=heightsSize;
}else{
right[j]=Top();
}
}
push(j);
}
int max=0;
for(int k=0;k<heightsSize;k++){
int sum=heights[k]*(right[k]-left[k]-1);
// printf("%d %d\n",left[k],right[k]);
if(sum>max)max=sum;
}
return max;
}
一次遍历
没出栈的是右边没有比他小的,则取默认值为 h e i g h t s S i z e heightsSize heightsSize
#define MaxSize 30000
typedef struct Stack{
int top;
int num[MaxSize];
}Stack;
Stack S;
void init(){
S.top=0;
}
void push(int a){
S.top++;
S.num[S.top]=a;
}
int pop(){
if(S.top==0)return 0;
S.top--;
return S.num[S.top+1];
}
int Top(){
return S.num[S.top];
}
int empty(){
if(S.top==0)return 1;
else return 0;
}
int largestRectangleArea(int* heights, int heightsSize){
init();
int *left=(int *)malloc((heightsSize+1)*sizeof(int));
int *right=(int *)malloc((heightsSize+1)*sizeof(int));
for(int w=0;w<heightsSize;w++){
right[w]=heightsSize;
}
for(int i=0;i<heightsSize;i++){
while(!empty()&&heights[Top()]>=heights[i]){
right[Top()]=i;
pop();
}
if(empty()){
left[i]=-1;
}else{
left[i]=Top();
}
push(i);
}
int max=0;
for(int k=0;k<heightsSize;k++){
int sum=heights[k]*(right[k]-left[k]-1);
//printf("%d %d\n",left[k],right[k]);
if(sum>max)max=sum;
}
return max;
}
31 对称二叉树
2020-5-31
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
[1,2,2,null,3,null,3] 则不是镜像对称的:
如果同时满足下面的条件,两个树互为镜像:
- 它们的两个根结点具有相同的值
- 每个树的右子树都与另一个树的左子树镜像对称
递归
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
bool check(struct TreeNode* p,struct TreeNode* q){
if (!p && !q) return true; //同时为空
if (!p || !q) return false;// 有一个不为空
return p->val==q->val&&check(p->left,q->right)&&check(p->right,q->left);
}
bool isSymmetric(struct TreeNode* root){
return check(root,root);
}
假设树上一共
n
n
n个节点
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
迭代 队列
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
#define MaxSize 10000
typedef struct queue {
struct TreeNode* data[MaxSize];
int front;
int rear;
}Queue;
Queue Q;
void initilize() {
Q.front = 0;
Q.rear = 0;
}
void push(struct TreeNode* root) {
Q.data[++Q.rear] = root;
}
struct TreeNode* pop() {
return Q.data[++Q.front];
}
int empty() {
return Q.rear == Q.front;
}
bool check(struct TreeNode* p,struct TreeNode* q){
initilize();
push(p);
push(q);
while(!empty()){
struct TreeNode* u=pop();
struct TreeNode* v=pop();
if (!u && !v) continue;
if((!u||!v)||u->val!=v->val)return false;
push(u->left);
push(v->right);
push(u->right);
push(v->left);
}
return true;
}
bool isSymmetric(struct TreeNode* root){
return check(root,root);
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
End 基础支持
calloc函数
函数原型:void* calloc(unsigned int num,unsigned int size);
功能:在内存的动态存储区中分配num个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。
一般使用后要使用 free(起始地址的指针) 对内存进行释放,不然内存申请过多会影响计算机的性能。如果使用过后不清零,还可以使用该指针对该块内存进行访问。
malloc函数
函数原型为void *malloc(unsigned int size);
功能:在内存的动态存储区中分配一个长度为size的连续空间。函数的返回值是分配区域的起始地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的开头位置。如果分配成功则返回指向被分配内存的指针(此存储区中的初始值不确定),否则返回空指针NULL。
当内存不再使用时,应使用free()函数将内存块释放。函数返回的指针一定要适当对齐,使其可以用于任何数据对象。
动态申请二维数组
https://blog.csdn.net/fengxinlinux/article/details/51541003
原因一:没有函数声明,且函数定义在主函数之后;原因二:头文件的被循环引用,在引用时考虑清楚包含顺序; 原因三:头文件函数声明和函数定义参数不同;原因四:函数使用的参数类型是自定义类型(如结构体),而自定义类型的定义在函数的声明和函数定义之间,由于在函数声明时,结构体并没有被定义,不被系统识别为结构体,而后面定义函数时,结构体已经定义,系统将其识别为结构体,导致系统认为声明和定义使用 的是不同的参数类型;所以才会出现上述问题; ↩︎