链表
1.删除单链表的重复节点
遍历法
class Solution {
public:
ListNode* removeDuplicateNodes(ListNode* head) {
//先检查头节点是否为空,快速判断
if (head == NULL) {
return NULL;
}
ListNode *current = head;
//循环遍历检查每一个元素,如果有相同元素则去掉
while (current) {
ListNode *p = current;
while (p->next) {
if (p->next->val == current->val) {
ListNode *toDelete = p->next;
p->next = p->next->next;
delete toDelete; // 释放内存
} else {
p = p->next;
}
}
current = current->next;
}
return head;
}
};
数组法:用一个数组指示,数组初始为0,一次遍历元素的值,出现就将数组对应值置1
2. 如何找出链表的倒数第K个元素
快慢指针法:快指针比慢指针快K个元素,当快指针指向末尾NULL时,此时慢指针指向倒数第K个元素。
class Solution {
public:
//快慢指针:空间换时间
ListNode* trainingPlan(ListNode* head, int cnt) {
ListNode* fast=head;
ListNode* slow=head;
while(cnt--)
{
fast=fast->next;
}
while (fast)
{
fast=fast->next;
slow=slow->next;
}
return slow;
}
};
3. 如何找出链表的中间节点
双指针:快指针走两步,满指针走一步
class Solution {
public:
//双指针法
ListNode* middleNode(ListNode* head) {
ListNode* fast=head;
ListNode* slow=head;
while((fast!=NULL)&&(fast->next!=NULL))
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
};
4. 反转链表
反转是逐个反转,例如反转1,2,3,4,5
先变成2,1,3,4,5
再3,2,1,4,5
依次直至最终反转
class Solution {
public:
//递归实现反转链表
ListNode* trainningPlan(ListNode* head) {
if(head==NULL || head->next==NULL){
return head;
}
else
{
struct ListNode* mid=head;
struct ListNode* lateer=mid->next;
head =trainningPlan(lateer);
lateer->next=mid;
mid->next=NULL;
return head;
}
}
};
5.环形链表
快慢指针:总会相遇,一个两部一个一步
class Solution {
public:
//快慢指针,追到就是了
bool hasCycle(ListNode *head) {
ListNode *fast=head;
ListNode *slow=head;
while((fast!=NULL)&&(fast->next!=NULL))
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
return true;
}
return false;
}
};
6. 单链表相交,如何求交点
移动相同的距离即是交点
class Solution {
public:
//这不是判断是否相交,比较简单,判断交点则AD+BD+DC
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *p=headA;
ListNode *q=headB;
while(p!=q)
{
if(p!=NULL)
p=p->next;
else
p=headB;
if(q!=NULL)
q=q->next;
else
q=headA;
}
return q;
}
};
7. 回文链表
找链表中间位置。将后半链表反转,依次和链表前一部分比较,看是否一致。
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (head == NULL) return true;
// 找到链表的中间节点
ListNode* fast = head;
ListNode* slow = head;
while (fast != NULL && fast->next != NULL) {
fast = fast->next->next;
slow = slow->next;
}
// 反转链表的后半部分
ListNode* mid = slow;
ListNode* prev = NULL;
while (mid != NULL) {
ListNode* nextTemp = mid->next;
mid->next = prev;
prev = mid;
mid = nextTemp;
}
// 将slow指向反转后链表的头节点
slow = prev;
// 比较前半部分和反转后的后半部分是否相同
fast = head;
while (slow != NULL) {
if (fast->val != slow->val) {
return false;
}
fast = fast->next;
slow = slow->next;
}
return true;
}
};
8. 算法实现两个有序链表的合并
递归实现,返回两个链表最小值,
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if(list1==NULL)
return list2;
if(list2==NULL)
return list1;
if(list1->val<list2->val)
{
list1->next= mergeTwoLists(list1->next,list2);
return list1;
}
else
{
list2->next=mergeTwoLists(list1,list2->next);
return list2;
}
}
};
树
树代码的核心操作在于递归
1.求二叉树的深度
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int calculateDepth(TreeNode* root) {
if(root ==NULL)
{
return 0;
}
int Lenleft=calculateDepth(root->left);
int lenright=calculateDepth(root->right);
return Lenleft>lenright?Lenleft+1:lenright+1;
}
};
2.如何判断二叉树是否相等
class Solution {
public:
//用某种遍历方法,存储成数组,遍历一棵树后,看数组是否相等
void preOrder(struct TreeNode *root,int *arr,int *i)
{
//用指针变量指示数组值的位置
(*i)++;
if(root==NULL)
{
*(arr+*i)=0;
return;
}
*(arr+*i)=root->val;
preOrder(root->left,arr,i);
preOrder(root->right,arr,i);
}
//前序遍历得到数组,再判断数组是否相等
bool isSameTree(TreeNode* p, TreeNode* q) {
int i,j;
int arr1[1000],arr2[1000];
memset(arr1,0,1000*sizeof(int));
memset(arr2,0,1000*sizeof(int));
if((p==NULL)&&(q==NULL))
return 1;
if((p==NULL)||(q==NULL))
return 0;
j=i=0;
preOrder(p,arr1,&i);
preOrder(q,arr2,&j);
for(i=0;i<1000;i++)
{
if(arr1[i]!=arr2[i])
{
return 0;
}
}
return 1;
}
};
3. 判断二叉树是否是平衡二叉树
挨个判断每个节点是否平衡,核心就是递归;
class Solution
{
public:
int deptmax(TreeNode* root)
{
if(root==NULL)
{
return 0;
}
int leftdept=deptmax(root->left);
int rightdept=deptmax(root->right);
return leftdept>rightdept?leftdept+1:rightdept+1;
}
//判断每一个节点的左右子树的深度,如何减少复杂度,实则判断meiyih
bool isBalanced(TreeNode* root) {
if(root==NULL)
{
return true;
}
int leftdept=deptmax(root->left);
int rightdept=deptmax(root->right);
if(abs(leftdept-rightdept)<2==false)
{
return false;
}
return abs(leftdept-rightdept)<2&&isBalanced(root->left)&&isBalanced(root->right);
}
};
数组
最大子序和
动态规划小问题,值得一看
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int pre=0,maxans=nums[0];
for(const auto &x:nums)
{
pre =max(pre+x,x);
maxans=max(maxans,pre);
}
return maxans;
}
};
挨个判断将新元素加入子序列的影响。
丢失的数字
哈希数组,异或,数学方法
题目可以有取巧的方法
class Solution {
public:
int missingNumber(vector<int>& nums) {
// 1. 计算从 0 到 n 的理论总和
int sum1 = (nums.size()+1) * (0 + nums.size()) / 2;
// 2. 计算当前数组的实际总和
int sum2 = 0;
for(int num : nums)
sum2 += num;
// 3. 返回差值,即为缺失的数字
return sum1 - sum2;
}
};
按奇偶排序数组
双指针法,交换元素
class Solution {
public:
//双指针法,快指针遍历元素,慢指针移动位置
vector<int> sortArrayByParity(vector<int>& nums) {
int fast=0;
int slow=0;
int cur=0;
for( fast=0;fast<nums.size();fast++)
{
if(nums[fast]%2==0)
{
cur=nums[slow];
nums[slow]=nums[fast];
nums[fast]=cur;
slow++;
}
}
return nums;
}
};
数组是是否存在重复元素
利用集合或者结构体哈希
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
int n = nums.size();
unordered_map<int, bool> set;
for(int i = 0; i < n; ++i){
if(i > k)
set[nums[i - k - 1]] = false;
if(set[nums[i]])
return true;
set[nums[i]] = true;
}
return false;
}
};
有序数组出现次数超过25%的元素
class Solution {
public:
//哈希法,数学方法,有序数组。满足条件仍然是原数字
int findSpecialInteger(vector<int>& arr) {
int n = arr.size();
for (int i = 0; i + n / 4 < n; ++i) {
if (arr[i] == arr[i + n / 4]) {
return arr[i];
}
}
return -1; // 如果没有找到符合条件的元素,返回-1
}
};
山脉数组
class Solution {
public:
//山脉数组,满足先增后减的数组;
//暴力法
bool validMountainArray(vector<int>& arr) {
int i=0;
if(arr.size()<=2)
return false;
for(i=1;i<arr.size();i++)
{
if(arr[i]-arr[i-1]<=0)
break;
if(i==arr.size()-1)
return false;
}
for(;i<arr.size();i++)
{
if(i==1)
return false;
if(arr[i]-arr[i-1]>=0)
return false;
}
return true;
}
};
最长连续递增序列
class Solution {
public:
//滑动窗口;特殊情况返回结果,不然就遍历一遍寻找最长的
int findLengthOfLCIS(vector<int>& nums) {
int numsSize=nums.size();
if(numsSize == 0)
return 0;
if(numsSize == 1)
return 1;
int length = 1;
int max = 1;
for(int i = 0; i < numsSize - 1; i++){
if(nums[i] < nums[i+1])
length++;
else
length = 1;
if(length > max)
max = length;
}
return max;
}
};
字符串
有效的括号
利用栈先进后出的思路来写
class Solution {
public:
// 括号匹配函数
bool isValid(const std::string& s) {
std::stack<char> stack;
for (char c : s) {
// 如果是左括号,将其入栈
if (c == '(' || c == '{' || c == '[') {
stack.push(c);
} else {
// 出现右括号时检查栈是否为空
if (stack.empty()) {
return false;
}
// 根据右括号类型检查栈顶是否匹配
if ((c == ')' && stack.top() != '(') ||
(c == '}' && stack.top() != '{') ||
(c == ']' && stack.top() != '[')) {
return false;
}
stack.pop(); // 匹配则出栈
}
}
// 栈为空则说明所有括号匹配
return stack.empty();
}
};
字符串相加
模拟成竖式相加,转换成整数类型来写
class Solution {
public:
string addStrings(string num1, string num2) {
int i = num1.length() - 1, j = num2.length() - 1, add = 0;
string ans = "";
while (i >= 0 || j >= 0 || add != 0) {
//强制转换成数字
int x = i >= 0 ? num1[i] - '0' : 0;
int y = j >= 0 ? num2[j] - '0' : 0;
int result = x + y + add;
ans.push_back('0' + result % 10);
add = result / 10;
i -= 1;
j -= 1;
}
// 计算完以后的答案需要翻转过来
reverse(ans.begin(), ans.end());
return ans;
}
};
二进制求和
和上一题一样的思路
class Solution {
public:
string addBinary(string num1, string num2) {
int i = num1.length() - 1, j = num2.length() - 1, add = 0;
string ans = "";
while (i >= 0 || j >= 0 || add != 0) {
//强制转换成数字
int x = i >= 0 ? num1[i] - '0' : 0;
int y = j >= 0 ? num2[j] - '0' : 0;
int result = x + y + add;
ans.push_back('0' + result % 2);
add = result / 2;
i -= 1;
j -= 1;
}
// 计算完以后的答案需要翻转过来
reverse(ans.begin(), ans.end());
return ans;
}
};
反转字符串
主要是掌握swap函数的用法,双指针法
class Solution {
//字符串向量,难点在于使用O(1)的额外空间解决这一问题;不然直接逆序push_back就好了;vector向量没有赋值的操作
//利用swap函数
public:
void reverseString(vector<char>& s) {
int left=0;
int right=s.size()-1;
while(left<right)
{
swap(s[left],s[right]);
left++;
right--;
}
}
};
反转字符串中的单词
思路和上一题差不多,最主要就是分别逆转单词的条件
class Solution {
public:
void reverseString(string& s,int left,int right) {
while(left<right)
{
swap(s[left],s[right]);
left++;
right--;
}
}
//仿照上题思路,主要在于找到每个单词的界限
string reverseWords(string s) {
int a=0;
for(int i=0;i<=s.size();i++)
{
if(s[i]==' '|| (i==s.size()))
{
reverseString(s,a,i-1);
a=i+1;
}
}
return s;
}
};
验证回文串
主要是掌握isanum(ch):用于验证ch是否是字母或数字,是则返回trtol
tolower库函数将大小字符转换成小写
class Solution {
public:
//先是处理字符串,然后再验证字符串。
//边处理边比较,更能节省时间复杂度。
bool isPalindrome(string s) {
string sgood;
for (char ch: s) {
if (isalnum(ch)) {
sgood += tolower(ch);
}
}
int n = sgood.size();
int left = 0, right = n - 1;
while (left < right) {
if (sgood[left] != sgood[right]) {
return false;
}
++left;
--right;
}
return true;
}
};
验证回文串2
using namespace std;
class Solution {
public:
bool isPalindrome(const string& s, int left, int right) {
while (left < right) {
if (s[left] != s[right]) {
return false;
}
left++;
right--;
}
return true;
bool validPalindrome(string s) {
int left = 0;
int right = s.size() - 1;
while (left < right) {
if (s[left] != s[right]) {
// 如果发现字符不匹配,检查删除左侧字符或右侧字符后的子字符串是
//否是回文
return isPalindrome(s, left + 1, right)
|| isPalindrome(s, left, right - 1);
}
left++;
right--;
}
return true;
}
};
根据字符出现频率排序
难点1在于了解unordered_map<char,int>类似字典的数据类型
难点2在于了解sort这种排序的算法
class Solution {
public:
string frequencySort(string s) {
unordered_map<char, int> mp;
for(auto ch:s) mp[ch]++;
sort(s.begin(),s.end(),[&](const char &a, const char &b){
return mp[a]==mp[b] ? a>b : mp[a]>mp[b];
});
return s;
}
};
前K个高频单词
1. 对于字符串可以利用unordered_map< , >数据类型
2. 取出所有键值对,将键值存储到变量当中
3.sort(数组起始,数组结束,[&]排序规则)
[&]字符串1,字符串2
{
排序规则(字符串对应的键值)
}
class Solution {
public:
vector<string> topKFrequent(vector<string>& words, int k) {
unordered_map<string, int> mp;
for (auto& str : words) {
mp[str]++;
}
vector<string> keys;
for (const auto& pair : mp) {
keys.push_back(pair.first);
}
sort(keys.begin(), keys.end(),
[&](const string& a, const string& b)
{
if (mp[a] == mp[b]) {
return a < b;
}
return mp[a] > mp[b];
});
return vector<string>(keys.begin(), keys.begin() + k);
}
};
计数二进制子串
代码思路不难考察的是数学思路;
根据题目判断符合子序列要求。
class Solution {
public:
int countBinarySubstrings(string s) {
int n=0,pre=0,cur=1,len=s.size()-1;
for(int i=0;i<len;i++)
{
if(s[i]==s[i+1])
{
++cur;
}
else
{
pre=cur;
cur=1;
}
if(pre>=cur)
++n;
}
return n;
}
};
实现字符串的库函数
atoi(将字符串转换成整数)
itoa:将整数转成字符串
排序算法及其改进方法
快速排序
中间轴加递归
交换函数,中枢轴的确定,交换排序,递归调用
#include <iostream>
#include <cstdlib> // 包含随机数生成的库
// 交换两个元素的值
void Swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
// 快速排序函数
void QuickSort(int arr[], int left, int right) {
// 如果数组只有一个元素或无元素,直接返回
if (left >= right) {
return;
}
// 随机选择枢轴,或固定选择左轴
int pivotIndex = left;// +rand() % (right - left + 1);
int pivot = arr[pivotIndex];
// i 指向小于枢轴的区域的末尾
int i = left;
// j 指向当前待检查的元素
int j = left + 1;
// 通过遍历将小于枢轴的元素移到左边,大于枢轴的元素移到右边
while (j <= right) {
//大于不交换,小于的时候把大于指向的元素进行交换
if (arr[j] < pivot) {
i++;
// 将小于枢轴的元素交换到左边
Swap(arr[i], arr[j]);
}
j++;
}
// 将枢轴放到正确的位置,交换
Swap(arr[left], arr[i]);
// 递归排序枢轴左侧和右侧的元素
QuickSort(arr, left, i - 1); // 排序左侧
QuickSort(arr, i + 1, right); // 排序右侧
}
// 打印数组
void PrintArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
// 初始化一个数组
int arr[] = { 50, 20, 60, 40, 10, 30 };
int size = sizeof(arr) / sizeof(arr[0]);
std::cout << "排序前的数组: ";
PrintArray(arr, size);
// 执行快速排序
QuickSort(arr, 0, size - 1);
std::cout << "排序后的数组: ";
PrintArray(arr, size);
return 0;
}
冒泡排序
遍历+逐个比较
#include <iostream>
// 带跳出条件的冒泡排序函数
void bubbleSort(int* arr, int n) {
for (int i = 0; i < n - 1; i++) {
bool swapped = false; // 标记是否发生交换
// 每次遍历,逐步将最大元素移到最后
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换相邻元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true; // 发生了交换
}
}
// 如果没有发生任何交换,说明数组已经有序,提前跳出
if (!swapped) {
break;
}
}
}
// 打印数组
void printArray(int* arr, int n) {
for (int i = 0; i < n; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
int arr[] = { 64, 34, 25, 12, 22, 11, 90 };
int n = sizeof(arr) / sizeof(arr[0]);
std::cout << "排序前的数组: ";
printArray(arr, n);
// 执行带跳出条件的冒泡排序
bubbleSort(arr, n);
std::cout << "排序后的数组: ";
printArray(arr, n);
return 0;
}
堆排序
插入排序
将元素插入到顺序表中,适用于链表,数组插入复杂度有点高,可以采用二分查找法
#include <iostream>
typedef int Mytype; // 定义类型Mytype为int,你也可以根据需要更改为其他类型
void InsertSort(Mytype a[], int n) {
// 注意从第二个元素开始
for (int i = 1; i < n; i++) {
// 如果a[i-1]本来就是小于a[i]的,就不需要做任何操作
if (a[i] < a[i - 1]) {
int j = i - 1;
int x = a[i]; // 暂存a[i]的值
// 从第j个元素开始,找一个比x小的位置
while (j >= 0 && x < a[j]) {
// 顺序查找同时将数组元素后移
a[j + 1] = a[j];
j--;
}
// 插入到正确的位置
a[j + 1] = x;
}
}
}
int main() {
Mytype arr[] = { 34, 8, 64, 51, 32, 21 };
int n = sizeof(arr) / sizeof(arr[0]);
std::cout << "Array before sorting: ";
for (int i = 0; i < n; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
InsertSort(arr, n);
std::cout << "Array after sorting: ";
for (int i = 0; i < n; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
选择排序
逐个遍历选择出未排序序列的极值进行排序
#include <iostream>
typedef int Mytype; // 定义类型 Mytype 为 int,可以根据需求更改
/**
* @Description: 从 i 的位置开始,寻找最大值的位置
* @Param: int arr[] 数组名, int i 查找最大值开始的位置, int n 数组长度
* @Return: 最大值的位置
* @Author: Carlos
*/
int FindMaxPos1(Mytype arr[], int i, int n) {
// 假设第 i 的位置为最大值
int max = i;
// 从 i+1 开始比较
while (i + 1 < n) {
if (arr[max] < arr[i + 1]) {
max = i + 1;
}
i++;
}
return max;
}
/**
* @Description: 从 i 的位置开始,寻找最小值的位置
* @Param: int arr[] 数组名, int i 查找最小值开始的位置, int n 数组长度
* @Return: 最小值的位置
* @Author: Carlos
*/
int FindMinPos1(Mytype arr[], int i, int n) {
// 假设第 i 的位置为最小值
int min = i;
// 从 i+1 开始比较
while (i + 1 < n) {
if (arr[min] > arr[i + 1]) {
min = i + 1;
}
i++;
}
return min;
}
int main() {
Mytype arr[] = { 34, 8, 64, 51, 32, 21 };
int n = sizeof(arr) / sizeof(arr[0]);
int maxPos = FindMaxPos1(arr, 0, n);
int minPos = FindMinPos1(arr, 0, n);
std::cout << "Maximum value is at position: " << maxPos << " with value: " << arr[maxPos] << std::endl;
std::cout << "Minimum value is at position: " << minPos << " with value: " << arr[minPos] << std::endl;
return 0;
}
归并排序
归并排序就是拆成多个子序列,然后递归调用归并进行排序,归并是归并已经排序好的,一开始是一个元素后面是半个子序列
#include <iostream>
#define MAXSIZE 10
using namespace std;
// 实现归并,并把最后的结果存放到list1里
void merging(int *list1, int list1_size, int *list2, int list2_size) {
int i, j, k, m;
int temp[MAXSIZE];
i = j = k = 0;
// 合并两个有序数组
while (i < list1_size && j < list2_size) {
if (list1[i] < list2[j]) {
temp[k++] = list1[i++];
} else {
temp[k++] = list2[j++];
}
}
// 复制剩余的元素
while (i < list1_size) {
temp[k++] = list1[i++];
}
while (j < list2_size) {
temp[k++] = list2[j++];
}
// 将合并的结果复制回list1
for (m = 0; m < (list1_size + list2_size); m++) {
list1[m] = temp[m];
}
}
// 递归实现的归并排序
void MergeSort(int k[], int n) {
if (n > 1) {
int *list1 = k;
int list1_size = n / 2;
int *list2 = k + n / 2;
int list2_size = n - list1_size;
// 对两个子数组分别进行归并排序
MergeSort(list1, list1_size);
MergeSort(list2, list2_size);
// 合并两个排序后的子数组
merging(list1, list1_size, list2, list2_size);
}
}
int main() {
int i, a[MAXSIZE] = {5, 2, 6, 0, 3, 9, 1, 7, 4, 8};
cout << "排序前的数组是:";
for (i = 0; i < MAXSIZE; i++) {
cout << a[i] << " ";
}
cout << endl;
// 调用递归版本的归并排序
MergeSort(a, MAXSIZE);
cout << "排序后的数组是:";
for (i = 0; i < MAXSIZE; i++) {
cout << a[i] << " ";
}
cout << endl;
return 0;
}
希尔排序
- 通过设置 gap 使得待排序的数组被分成若干个子序列,每个子序列独立排序。
- 随着 gap 不断减小,最后的排序是对整个数组进行的插入排序。
- 这种排序方式比单纯的插入排序更快,尤其在数组接近有序的情况下。
#include <iostream>
using namespace std;
/**
* @Description: 希尔排序算法(升序排序)
* @Param: int arr[] 数组名, int len 数组长度
* @Return: void
*/
void shell_sort(int arr[], int len) {
int gap, i, j;
int temp;
// 初始化 gap(间隔),每次将 gap 减小一半
for (gap = len / 2; gap > 0; gap /= 2) {
// 从 gap 开始,对每个元素进行插入排序
for (i = gap; i < len; i++) {
temp = arr[i]; // 暂存当前元素
// 通过 gap 进行插入排序
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap]; // 向右移动元素
}
arr[j] = temp; // 插入到正确位置
}
}
}
int main() {
int arr[] = { 12, 34, 54, 2, 3 };
int len = sizeof(arr) / sizeof(arr[0]);
cout << "排序前的数组是:";
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
// 调用希尔排序算法
shell_sort(arr, len);
cout << "排序后的数组是:";
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
return 0;
}