力扣题目阶段性总结
这段时间对之前的题目都有一个复习,我现在对着之前做过的题目进行一个阶段性的总结
双指针类型
双指针主要都是在解决数组和链表问题中常用的方法,我们通过双指针来实现一个降低时间复杂度的效果。
11.盛最多水的容器
这道题目是设立两个指针分别从左右两侧向中间移动,这样来确定一个所盛水的大小,一个指针从头开始,一个指针从末尾开始。
int maxArea(int* height, int heightSize) {
int i = 0;
int j = heightSize - 1;
int sum = 0;
int tmp = 0;
while (i <= j) {
tmp = fmin(height[i], height[j]) * (j - i);
if (sum < tmp) {
sum = tmp;
}
if (height[i] < height[j]) {
i++;
}
else {
j--;
}
}
return sum;
}
19. 删除链表倒数的第N个节点
这道题目是对于链表的一个操作,我们要从头开始设立一个虚拟头节点,然后运用一个快指针,一个慢指针,向后遍历一个找到删除节点的前一个节点,一个走到尾部。我们的主要目的是让一个节点走到链表尾的时候。这个过程我们让快指针先走n步,这样在快指针走到结尾的时候,我们不难看出慢指针会走到那个被删除节点的前一个节点。
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
struct ListNode* dummyhead = (struct ListNode*)malloc(sizeof(struct ListNode));
if (head == NULL) { //链表的问题我们要明白可能会传入一个空链表
return head;
}
dummyhead->next = head;
struct ListNode* fast = dummyhead;
struct ListNode* slow = dummyhead;
n++;
while (n) {
fast = fast->next;
n--;
}
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return dummyhead->next;
}
27 移除元素
这道题目是,用快慢指针这两个指针遍历数组,如果找到需要删除的值,我们就将只移动快指针,其他情况就是同时移动两个指针一个一个赋值。
int removeElement(int* nums, int numsSize, int val) {
int i = 0;
int j = 0;
for (; i < numsSize; i++) {
if (nums[i] != val) {
nums[j] = nums[i];
j++;
}
}
return j;
}
141 环形链表
这道题目主要是运用一个快慢指针进行一个遍历链表,我们通过这两个指针一个一次进行两步,一个一次一步,如果链表中有环,他们自己会在环中相遇。
bool hasCycle(struct ListNode *head) {
if (head == NULL || head->next == NULL) {
return false;
}
struct ListNode* fast = head->next;
struct ListNode* slow = head;
while (fast != NULL && fast->next != NULL) {
//如果不成环,我们要注意我们的判断,因为一次两步,所以我们要对于fast->next进行判断。
if (fast == slow) { //找到两个指针相遇
return true;
}
slow = slow->next;
fast = fast->next->next;
}
return false;
}
142 环形链表 2
这里涉及到一个数学问题,也就是我们要考虑到找到环之后,该怎么寻找起点的问题。我们来进行一个推导。假设:slow指针走过的节点数为: x + y。fast指针走过的节点数: x + y + n (y + z)。这两个节点的节点数:(x + y) * 2 = x + y + n (y + z)。x + y = n (y + z)。这时候我们不难发现当n = 1时候。x = z。就意味着我们的从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。这就是这道题目的核心。
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode* fast = head;
struct ListNode* slow = head;
struct ListNode* index1 = head;
struct ListNode* index2 = head;
while(fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
index1 = fast;
index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index2;
}
}
return NULL;
}
977 有序数组的平方
这道题目因为是已经排序好的数组,所以我们不难看出我们通过一个最左边和最右边的指针,两个的绝对值是最大的我们可以比较其平方大小来进行赋值我们新建的数组,那个大就先移动那个数组。
int* sortedSquares(int* nums, int numsSize, int* returnSize) {
int* array = (int*)malloc(sizeof(int) * numsSize);
int i = 0;
int j = numsSize - 1;
int k = numsSize - 1;
while (i <= j) {
if (nums[i] * nums[i] < nums[j] * nums[j]) {
array[k--] = nums[j] * nums[j];
j--;
}
else {
array[k--] = nums[i] * nums[i];
i++;
}
}
*returnSize = numsSize;
return array;
}
15 三数之和
这道题目是双指针章节对于我而言最难的一道题目,这道题目不仅要考虑去重,还要对于原数组进行排序来减少时间复杂度。对于C语言而言,我们还会遇到一个创建二维数组比较困难的问题。
我们先要对这个进行一个排序,这样之后,我们才可以对其进行双指针的操作(不然我们直接使用会出现一个重复的问题),因为排序后的数组是一个升序前面的最小,后面的最大,我们可以先选中一个一个数字,后面在除了这个数字之外再进行一个双指针减少复杂度的操作,一个 j 选中 i 后面一个数字,另一个k 选中 最后面一个数然后向前前进。这样就可以实现一个求和。我们接下来对根据代码进行详细分析。
int cmp(const void* a, const void* b) {
if (*(int*)a > *(int*)b) {
return 1;
}
else if (*(int*)a == *(int*)b) {
return 0;
}
else {
return -1;
}
}
int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
int base = 100;
qsort(nums, numsSize, sizeof(int), cmp); //排序算法,将数组变成一个升序数组
int** array = (int**) malloc(sizeof(int*) * 100); //二维数组的创建
*returnColumnSizes = (int*)malloc(sizeof(int) * base); //先分配内存
*returnSize = 0;
int sum;
int j,k;
for (int i = 0; i < numsSize; i++) {
if(i > 0 && nums[i]==nums[i-1]) //进行一个去重操作,因为排序后的数组我们才能进行去重
continue;
j = i + 1;
k = numsSize - 1;
while (j < k) {
sum = nums[i] + nums[j] + nums[k];
if (sum == 0) {
array[*returnSize]=(int*)malloc(sizeof(int)*3);
(*returnColumnSizes)[*returnSize]=3; //对行数组中的列的个数赋值
array[*returnSize][0] = nums[i];
array[*returnSize][1] = nums[j];
array[*returnSize][2] = nums[k];
(*returnSize)++;
if(*returnSize == base){ //扩容数组
base*=2;
array=(int**)realloc(array,sizeof(int*)*base);
*returnColumnSizes=(int*)realloc(*returnColumnSizes,sizeof(int)*base);
}
int num1 = nums[j];
int num2 = nums[k];
while(nums[j]==num1&&j<k) //去重操作
j++;
while(nums[k]==num2&&j<k) //去重操作
k--;
}
if (sum < 0) {
j++;
}
else if (sum > 0) {
k--;
}
}
}
return array;
}
二分 类型
二分查找是一种常见的题目类型,主要考虑两个判断条件,一个是循环不变量,另一个是在改变区间的时候,我们要找到那个条件。
1283 使结果不超过阈值的最小除数
这道题目主要是,我们要明白查询的区间范围,然后在向上取整的地方左移小操作。我们查询的范围是数组里的最大数字和1构成的闭区间。
int allsum(int* nums, int a, int numsSize) {
int tmp = 0;
for (int i = 0; i < numsSize; i++) {
tmp += (nums[i] + a - 1) / a;
}
return tmp;
}
int smallestDivisor(int* nums, int numsSize, int threshold) {
int x = 1;
int left = 1;
int right;
for (int i = 0; i < numsSize; ++i) {
right = fmax(right, nums[i]);
}
int sum = 0;
int mid = (left + right) / 2;
while (left <= right) {
sum = allsum(nums, mid, numsSize);
if (sum > threshold) {
left = mid + 1;
}
else {
right = mid - 1;
}
mid = (left + right) / 2;
}
return right + 1;
}
704 二分查找
这道题目是二分查找的模版。
int search(int* nums, int numsSize, int target) {
int left = 0;
int right = numsSize - 1;
int mid = (left + right) / 2;
while (left <= right) {
if (nums[mid] < target) {
left = mid + 1;
mid = (left + right) / 2;
}
else if (nums[mid] == target) {
return mid;
}
else {
right = mid - 1;
mid = (left + right) / 2;
}
}
return -1;
}
69 x的平方根
其实是找到第一个平方数小于x的数字。
int mySqrt(int x){
long long left,right;
left = 1;
right = x/2;
long long mid;
if(x == 1){
return 1;
}
while(left <= right){
mid = (left + right) /2;
if(mid * mid == x){
return mid;
}
else if(mid * mid < x){
left = mid + 1;
}
else{
right = mid - 1;
}
}
return right;
}
153 寻找旋转排序数组中的最小值
这道题目的思路是比较左,中,右三个的数据大小是否按顺序排列
int findMin(int* nums, int numsSize) {
int low = 0;
int high = numsSize - 1;
while (low < high) {
int pivot = low + (high - low) / 2;
if (nums[pivot] < nums[high]) {
high = pivot;
} else {
low = pivot + 1;
}
}
return nums[low];
}
位运算系列
137 只出现过一次的数字 ||
这道题目运用了每一个二进制上的位置上的数字相加,因为那个三个相同数字可以被三整除,如果一个二进制上的数字不能被3整除的话就意味着,这个数的余数就是我们要求的数字多出来的数字
int singleNumber(int* nums, int numsSize) {
int total = 0;
int sum = 0;
for (int i = 0; i < 32; i++) {
total = 0;
for (int j = 0; j < numsSize; j++) {
total += (nums[j] >> i) & 1;
}
if (total % 3) {
sum |= (1u << i);
}
}
return sum;
}
136 只出现一次的数字
这个题目运用异或两个相同的数字的结果是0,我们把所有的数字全部的异或一次,剩下的数字便是我们的结果,也就是我们的结果。
int singleNumber(int* nums, int numsSize) {
int ans = 0;
for (int i = 0; i < numsSize; i++) {
ans ^= nums[i];
}
return ans;
}
栈系列
20 有效括号
这道题目考得是一个栈的简单应用,应用的是一个栈先进后出的特性。
bool isValid(char* s) {
int a[100000];
int top = -1;
top++;
a[top] = *s;
s++;
while (*s != '\0') {
if (*s == ')' &&top != -1&& a[top] == '(') {
top--;
s++;
}
else if (*s == ']' && top != -1 && a[top] == '[') {
top--;
s++;
}
else if (*s == '}' && top != -1 && a[top] == '{') {
top--;
s++;
}
else {
top++;
a[top] = *s;
s++;
}
}
if (top == -1) {
return true;
}
return false;
}
150 逆波兰表达式
这道题目是栈的一个应用,也是栈的特色先进后出的特征。
bool isNumber(char* token) {
return strlen(token) > 1 || ('0' <= token[0] && token[0] <= '9');
}
int evalRPN(char** tokens, int tokensSize) {
int n = tokensSize;
int stk[n], top = 0;
for (int i = 0; i < n; i++) {
char* token = tokens[i];
if (isNumber(token)) {
stk[top++] = atoi(token);
} else {
int num2 = stk[--top];
int num1 = stk[--top];
switch (token[0]) {
case '+':
stk[top++] = num1 + num2;
break;
case '-':
stk[top++] = num1 - num2;
break;
case '*':
stk[top++] = num1 * num2;
break;
case '/':
stk[top++] = num1 / num2;
break;
}
}
}
return stk[top - 1];
}
字符串系列
28 strstr()的实现
这道题目是kmp算法的学习过程中写的题目。
int strStr(char* haystack, char* needle) {
int n = strlen(haystack);
int m = strlen(needle);
if (m == 0) {
return 0;
}
int next[m];
next[0] = -1;
for (int i = 1, j = -1; i < m; i++) { //
while (needle[i] != needle[j + 1] && j != -1) {
j = next[j];
}
if (needle[i] == needle[j + 1]) {
j++;
}
next[i] = j;
}
for (int i = 0, j = -1; i < n; i++) {
while (haystack[i] != needle[j + 1] && j != -1) {
j = next[j];
}
if (haystack[i] == needle[j + 1]) {
j++;
}
if (j == m - 1) {
return i - m + 1;
}
}
return -1;
}
151 反转字符串中的单词
这道题目我们可以用两种做法一种是创建额外空间用一个二维数组进行分割字符串。另一种是我们通过翻转多次字符串,实现一个反转单词的效果。
char *reverseWords(char *s) { //方法一
char tmp[5000][5000];
int j = 0;
int flag = 0;
int n = 0;
int tmp1 = 0;
int tmp2 = strlen(s) - 1;
// 跳过前导空格
while (s[tmp1] == ' ') {
tmp1++;
}
while (s[tmp2] == ' ') {
tmp2--;
}
for (int i = tmp1; i < tmp2 + 1; i++) {
if (s[i] == ' ' && flag == 0) {
tmp[j][n] = '\0';
n = 0;
j++;
flag = 1;
}
else if (s[i] != ' ') {
tmp[j][n++] = s[i];
flag = 0;
}
}
// 处理末尾的单词
tmp[j][n] = '\0';
j++;
// 分配足够的内存给 array 指针
char *array = (char *)malloc(sizeof(char) * (strlen(s) + 1));
int k = 0;
for (int i = j - 1; i >= 0; i--) {
for (int m = 0; m < strlen(tmp[i]); m++) {
array[k++] = tmp[i][m];
}
// 添加空格,除了最后一个单词
if (i != 0) {
array[k++] = ' ';
}
}
array[k] = '\0'; // 添加字符串结尾的 null 字符
return array;
}
char *reverseWords(char *s) { //方法2
int start = 0;
int len = strlen(s) - 1;
while (s[start] == ' ') { //删除开头空格
start++;
}
int slow = 0;
while (s[len] == ' ') { // 删除尾部空格
len--;
}
for (int k = start; k <= len; k++) { //用一个双指针的方法对数组一个重新赋值
if (s[k] == ' ' && s[k + 1] == ' ') {
continue;
}
s[slow] = s[k];
slow++;
}
s[slow] = '\0';
int i = 0;
int end = slow - 1;
int flag = 0;
char tmp;
while (i < end) { //先对全部数组进行一个删除
tmp = s[i];
s[i] = s[end];
s[end] = tmp;
i++;
end--;
}
i = 0;
for (int j = 0; j < slow; j++) { //翻转单词
if (s[j] == ' ') {
end = j - 1;
while (i <= end) {
char a = s[i];
s[i] = s[end];
s[end] = a;
i++;
end--;
}
i = j + 1;
}
}
end = slow - 1;
while (i < end) {
tmp = s[i];
s[i] = s[end];
s[end] = tmp;
i++;
end--;
}
return s;
}
541 翻转字符串2
这道题目是一个多次翻转的问题,我们只要理解题目意思按部就班就可以完成这道题目。
char* reverseStr(char* s, int k) {
int i = 2 * k;
int j;
int n = strlen(s);
int left;
int right;
char tmp;
if (n < k) {
left = 0;
right = n - 1;
while (left < right) {
tmp = s[left];
s[left] = s[right];
s[right] = tmp;
left++;
right--;
}
return s;
}
for (j = 0; j < n && n - j >= k;) {
left = j;
right = i - 1 - k;
while (left < right) {
tmp = s[left];
s[left] = s[right];
s[right] = tmp;
left++;
right--;
}
j = i;
i = i + 2 * k;
}
if (n - j <= k) {
left = j;
right = n - 1;
while (left < right) {
tmp = s[left];
s[left] = s[right];
s[right] = tmp;
left++;
right--;
}
}
return s;
}
这些题目是对于我之前题目的一个总结,我对之前做过的题目进行了一个回顾,之后还对相关题目进行一个整理。