目录
将有序数组转换为二叉搜索树
解法:递归
找中间值构建根节点,
根节点的左右子树分别递归左区间与右区间
返回根节点
cpp
class Solution
{
public:
TreeNode *dfs(vector<int> &nums, int begin, int end)
{
if (begin > end)
{
return nullptr;
}
int mid = begin + (end - begin) / 2;
TreeNode *node = new TreeNode(nums[mid]);
node->left = dfs(nums, begin, mid - 1);
node->right = dfs(nums, mid + 1, end);
return node;
}
TreeNode *sortedArrayToBST(vector<int> &nums)
{
return dfs(nums, 0, nums.size() - 1);
}
};
排序链表
思路1:归并排序
快慢双指针找中间节点(不推荐通过计算中间值,遍历的方式找中间节点容易乱),分为两个链表
两个链表通过递归后变成两个有序链表
进行有序链表合并为一个有序链表
返回合并后的新链表
cpp
class Solution
{
public:
// 不使用数组的方法找中点,链表适合快慢双指针找中点
ListNode *MergeSort(ListNode *begin)
{
if (!begin || !begin->next)
{
return begin;
}
// 这里要保存mid落在第一个链表的尾节点上,所有end要初始化时要先走两步
// 不然 4->2 的情况下会死递归
ListNode *mid = begin, *end = begin->next->next;
while (end && end->next)
{
mid = mid->next;
end = end->next->next;
}
end = mid->next;
mid->next = nullptr;
ListNode *begin1 = MergeSort(begin);
ListNode *begin2 = MergeSort(end);
// 合并 begin1 和 begin2
ListNode ret(-1);
ListNode *tail1 = &ret;
while (begin1 || begin2)
{
if (begin1 && begin2)
{
if (begin1->val < begin2->val)
{
tail1->next = begin1;
begin1 = begin1->next;
}
else
{
tail1->next = begin2;
begin2 = begin2->next;
}
tail1 = tail1->next;
}
else if (begin1)
{
tail1->next = begin1;
begin1 = begin1->next;
tail1 = tail1->next;
}
else
{
tail1->next = begin2;
begin2 = begin2->next;
tail1 = tail1->next;
}
}
return ret.next;
}
ListNode *sortList(ListNode *head)
{
return MergeSort(head);
}
};
思路2:归并排序非递归
把原链表采用头节点的方式管理起来,方便后续合并前后找链表头尾节点
使用变量 step 代表每次合并链表的长度,每次 *=2,但 step >= 链表长度就可以返回答案
每次遍历使用 tail 和 cur 进行标记:
tail:合并完成的新有序链表插入到它后面,之后更新tail到新有序链表的尾节点中
cur:找当前待合并的两个step长的链表,之后更新到第二段链表尾节点的next节点上
注意:
cur 找第二段链表可能为空,head2 = cur->next 要特判
cur 找下一组时为空了,nextHead = cur->next 也要特判下

cpp
class Solution
{
public:
ListNode *sortList(ListNode *head)
{
if (!head || !head->next)
{
return head;
}
ListNode *tail = head;
int cnt = 0;
while (tail)
{
cnt++;
tail = tail->next;
}
ListNode ret(0, head);
for (int step = 1; step < cnt; step *= 2)
{
// tail后面跟的是合并完成的有序链表
ListNode *tail = &ret;
// cur当前节点开始,按照step分出两段长step的链表
ListNode *cur = tail->next;
while (cur)
{
// 第一段
ListNode *head1 = cur;
for (int i = 1; i < step && cur; i++)
{
cur = cur->next;
}
// 第二段
ListNode *head2 = nullptr;
// 原链表[1,2] 第一段 [1,2]
if (cur)
{
head2 = cur->next;
cur->next = nullptr;
}
cur = head2;
for (int i = 1; i < step && cur; i++)
{
cur = cur->next;
}
// 保存下一组起点
ListNode *NextHead = nullptr;
// 原链表[1,2] cur在2后面了
if (cur)
{
NextHead = cur->next;
cur->next = nullptr;
}
// 合并的新节点插入到tail后面
tail->next = Merget(head1, head2);
// 更新下一组起点为结束
cur = NextHead;
while (tail->next)
{
tail = tail->next;
}
}
}
return ret.next;
}
ListNode *Merget(ListNode *begin1, ListNode *begin2)
{
ListNode tmp;
ListNode *tail = &tmp;
while (begin1 && begin2)
{
if (begin1->val < begin2->val)
{
tail->next = begin1;
begin1 = begin1->next;
}
else
{
tail->next = begin2;
begin2 = begin2->next;
}
tail = tail->next;
}
tail->next = begin1 == nullptr ? begin2 : begin1;
return tmp.next;
}
};
合并k个升序链表
解法:模拟
使用优先级队列(小堆)的方式实现,每次拿出最小的进行尾插,把next加入到堆中
cpp
class Solution
{
public:
struct CmpNodeVal
{
bool operator()(ListNode *node1, ListNode *node2)
{
return node1->val > node2->val;
}
};
ListNode *mergeKLists(vector<ListNode *> &lists)
{
priority_queue<ListNode *, vector<ListNode *>, CmpNodeVal> q;
for (auto &list : lists)
{
if (list)
q.push(list);
}
ListNode head;
ListNode *tail = &head;
while (!q.empty())
{
auto tmp = q.top();
q.pop();
tail->next = tmp;
if (tmp->next)
q.push(tmp->next);
tmp->next = nullptr;
tail = tmp;
}
return head.next;
}
};
建立四叉树
解法:递归
dfs(0, 0 , n -1, m -1)表示以左上角(0,0)到 右下角(n-1,m-1)坐标下建立四叉树,返回根节点
如果(0,0)和(n-1,m-1)包围的面积值都相同,直接构建根节点返回
否则就先构建根节点
依次构建左右上下节点,也就是进行递归...难就难在 左上坐标和右下坐标的传参:先根据当前坐标计算出行和列之和再取中间值,根据规律选择+中间值(可能还要-1才适合)或者不加直接传参
cpp
class Solution
{
public:
Node *dfs(vector<vector<int>> &grid, int leftX, int leftY, int rightX, int rightY)
{
bool ok = true;
int tmp = grid[leftX][leftY];
for (int i = leftX; i <= rightX; i++)
{
for (int j = leftY; j <= rightY; j++)
{
if (grid[i][j] != tmp)
{
ok = false;
break;
}
}
}
if (ok)
{
return new Node(tmp, true);
}
int n = rightX - leftX + 1, m = rightY - leftY + 1;
Node *root = new Node(tmp, false);
root->topLeft = dfs(grid, leftX, leftY, leftX + n / 2 - 1, leftY + m / 2 - 1);
root->topRight = dfs(grid, leftX, leftY + m / 2, leftX + n / 2 - 1, rightY);
root->bottomLeft = dfs(grid, leftX + n / 2, leftY, rightX, leftY + m / 2 - 1);
root->bottomRight = dfs(grid, leftX + n / 2, leftY + m / 2, rightX, rightY);
return root;
}
Node *construct(vector<vector<int>> &grid)
{
int n = grid.size();
return dfs(grid, 0, 0, n - 1, n - 1);
}
};
最大子数组之和
解法:动态规划
dp[i]:以当前i为结尾的最大子数组之和,dp[i] = max(dp[i - 1] + nums[i], nums[i])
使用 ret 每个位置的最大和取max值后返回
cpp
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n+1, 0);
int ret = INT_MIN;
for(int i = 1; i <= n; i++)
{
dp[i] = max(dp[i-1] + nums[i-1], nums[i-1]);
if(dp[i] > ret)
{
ret = dp[i];
}
}
return ret;
}
};
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
int n = nums.size();
int ret = nums[0], tmp = nums[0];
for (int i = 1; i < n; i++)
{
tmp = max(tmp + nums[i], nums[i]);
if (tmp > ret)
{
ret = tmp;
}
}
return ret;
}
};
环形子数组的最大和
解法:正难则反
- 1:不是环形数组的情况(也就是没有边界情况)下最大和是多少(上面的思路)
- 2:是环形数组的情况(也就是有边界情况)下:先计算环形子数组的最小和,再使用总和减去最小和就得到结果
最终的答案是两种情况取最大值(如果出现第二种情况为0,就直接返回第一种情况)
cpp
class Solution
{
public:
int maxSubarraySumCircular(vector<int> &nums)
{
int n = nums.size();
int prevMin = nums[0], curMin = nums[0], prevMax = nums[0], curMax = nums[0], sum = nums[0];
for (int i = 1; i < n; i++)
{
sum += nums[i];
prevMin = min(prevMin + nums[i], nums[i]);
curMin = min(curMin, prevMin);
prevMax = max(prevMax + nums[i], nums[i]);
curMax = max(curMax, prevMax);
}
if (sum == curMin)
{
return curMax;
}
int cnt = max(curMax, sum - curMin);
return cnt;
}
};
搜索插入位置
解法:二分
右端点模板解决
cpp
class Solution
{
public:
int searchInsert(vector<int> &nums, int target)
{
int n = nums.size();
int left = 0, right = n - 1;
while (left < right)
{
int mid = left + (right - left) / 2;
if (nums[mid] >= target)
{
right = mid;
}
else
{
left = mid + 1;
}
}
// 特判
if (nums[left] < target)
{
return left + 1;
}
return left;
}
};
搜索二维矩阵
思路1:
从原点开始,右下 和 下右 这两种情况来找 target 看看该值是否存在
cpp
class Solution
{
public:
int t;
int rowFindTarget(int jBegin, int jEnd, int row, vector<vector<int>> &matrix)
{
while (jBegin < jEnd)
{
int mid = jBegin + (jEnd - jBegin + 1) / 2;
if (matrix[row][mid] <= t)
{
jBegin = mid;
}
else
{
jEnd = mid - 1;
}
}
return jBegin;
}
int colFindTarget(int iBegin, int iEnd, int col, vector<vector<int>> &matrix)
{
while (iBegin < iEnd)
{
int mid = iBegin + (iEnd - iBegin + 1) / 2;
if (matrix[mid][col] <= t)
{
iBegin = mid;
}
else
{
iEnd = mid - 1;
}
}
return iBegin;
}
bool searchMatrix(vector<vector<int>> &matrix, int target)
{
int n = matrix.size(), m = matrix[0].size();
t = target;
// [0,0]开始,右下二分
int jBegin = rowFindTarget(0, m - 1, 0, matrix);
int iBegin = colFindTarget(0, n - 1, jBegin, matrix);
if (matrix[iBegin][jBegin] == target)
{
return true;
}
// [0,0]开始,下右二分
iBegin = colFindTarget(0, n - 1, 0, matrix);
jBegin = rowFindTarget(0, m - 1, iBegin, matrix);
cout << iBegin << ' ' << jBegin << endl;
if (matrix[iBegin][jBegin] == target)
{
return true;
}
return false;
}
};
思路2:
排除法,从右上角开始:(当坐标不越界时)
如果当前位置 matrix[i][j] < target,i++
如果当前位置 matrix[i][j] > target,j--
剩下的情况就是找到了target,自己返回true
如果没有找到的情况下就返回 false
寻找峰值
思路:二分
按照右端点的思路来实现,比较 nums[i] 与 nums[i+1] 来移动位置
cpp
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int n = nums.size();
int left = 0, right = nums.size() - 1;
while(left < right)
{
int mid = left + (right - left)/2;
if(nums[mid] < nums[mid + 1])
{
left = mid + 1;
}
else
{
right = mid;
}
}
return left;
}
};
搜索旋转排序数组
解法:二分(右端点实现)
判断 nums[left] 与 nums[mid] 是在排序左区间还是右区间,再分别看target是否再该区间,从而来移动left 或者 right 的位置
cpp
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size();
int left = 0,right = n-1;
while(left<right)
{
int mid = left + (right-left)/2;
if(nums[left] <= nums[mid])
{
if(nums[left] <= target && target <= nums[mid])
{
right = mid;
}
else
{
left = mid + 1;
}
}
else
{
// 实现右端点left移动判断的是target在 [mid+1,right]
if(nums[mid] < target && target <= nums[right])
{
left = mid + 1;
}
else
{
right = mid;
}
}
}
if(nums[left] == target) return left;
return -1;
}
};
在排序数组中找第一个位置和最后一个位置
解法:二分
左右端点结合
cpp
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int n = nums.size();
if(n == 0)
{
return {-1, -1};
}
int left = 0, right = n - 1;
int ret_left = 0, ret_right = 0;
while(left < right)
{
int mid = left + (right - left) / 2;
if(nums[mid] >= target)
{
right = mid;
}
else
{
left = mid + 1;
}
}
if(nums[left] != target)
{
ret_left = -1;
}
else
{
ret_left = left;
}
left = 0, right = n - 1;
while(left < right)
{
int mid = left + (right - left + 1) / 2;
if(nums[mid] <= target)
{
left = mid;
}
else
{
right = mid - 1;
}
}
if(nums[left] != target)
{
ret_right = -1;
}
else
{
ret_right = left;
}
return {ret_left, ret_right};
}
};
寻找旋转数组的最小值
解法:二分
左端点进行解题,不过难在判断什么时候需要移动left的位置?
当left和mid在排序左区间且二者都大于right的位置时,此时最小值一定在mid右区间
cpp
class Solution {
public:
int findMin(vector<int>& nums) {
int n = nums.size();
int l = 0, r = n - 1;
while(l < r)
{
int mid = l + (r - l) / 2;
// 当前[l,mid]处于左区间
if(nums[l] <= nums[mid] && nums[l] > nums[r] && nums[mid] > nums[r])
{
l = mid + 1;
}
else
{
r = mid;
}
}
return nums[l];
}
};
寻找正序数组的中位数
思路1:模拟
合并正序数组位一个大的正序数组,计算中位数
cpp
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
vector<int> ret;
int m = nums1.size(), n = nums2.size();
if(m + n == 0)
{
return 0;
}
int i = 0, j = 0;
while(i < m && j < n)
{
if(nums1[i] < nums2[j])
{
ret.push_back(nums1[i++]);
}
else
{
ret.push_back(nums2[j++]);
}
}
while(i < m)
{
ret.push_back(nums1[i++]);
}
while(j < n)
{
ret.push_back(nums2[j++]);
}
if((m + n) % 2 != 0)
{
return ret[(m + n) / 2];
}
else
{
return (double)(ret[(m+n) / 2] + ret[(m+n) / 2 - 1]) / 2;
}
}
};
思路2:二分
优化前
cpp
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if(nums1.size() > nums2.size())
{
swap(nums1, nums2);
}
int m = nums1.size(), n = nums2.size();
nums1.insert(nums1.begin(), INT_MIN);
nums1.push_back(INT_MAX);
nums2.insert(nums2.begin(), INT_MIN);
nums2.push_back(INT_MAX);
int totalLeftWithSentinels = (m + n + 1) / 2;
int nums1_left = 0, nums1_right = m + 1;
while(nums1_left < nums1_right)
{
int nums1_mid = nums1_left + (nums1_right - nums1_left + 1) / 2;
int nums2_mid = totalLeftWithSentinels - nums1_mid;
if(nums1[nums1_mid] <= nums2[nums2_mid + 1])
{
nums1_left = nums1_mid;
}
else
{
nums1_right = nums1_mid - 1;
}
}
int i = nums1_left;
int j = totalLeftWithSentinels - i;
int a = max(nums1[i], nums2[j]);
if((m + n) % 2 != 0)
{
return a;
}
int b = min(nums1[i+1], nums2[j+1]);
return (double)(a + b) / 2;
}
};
优化后
cpp
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if(nums1.size() > nums2.size())
{
swap(nums1, nums2);
}
int m = nums1.size(), n = nums2.size();
// nums1.insert(nums1.begin(), INT_MIN);
// nums1.push_back(INT_MAX);
// nums2.insert(nums2.begin(), INT_MIN);
// nums2.push_back(INT_MAX);
int totalLeftWithSentinels = (m + n + 1) / 2;
int nums1_left = 0 - 1, nums1_right = m + 1 - 1;// 映射
while(nums1_left < nums1_right)
{
int nums1_mid = nums1_left + (nums1_right - nums1_left + 1) / 2;
// 这里映射难点:num1_mid要映射,num2_mid也要映射
int nums2_mid = totalLeftWithSentinels - (nums1_mid + 1) - 1;
if(nums1_mid < m && nums1[nums1_mid] <= nums2[nums2_mid + 1])
{
nums1_left = nums1_mid;
}
else
{
nums1_right = nums1_mid - 1;
}
}
// 这里还要处理越界问题
int pos1 = nums1_left, pos2 = (n + m + 1) / 2 - (nums1_left + 1) - 1;
int a1 = pos1 < 0 ? INT_MIN : nums1[pos1], b1 = pos2 < 0 ? INT_MIN : nums2[pos2];
int a2 = pos1 + 1 >= m ? INT_MAX : nums1[pos1 + 1], b2 = pos2 + 1 >= n ? INT_MAX : nums2[pos2 + 1];
double ret1 = max(a1, b1), ret2 = min(a2, b2);
return (n + m) % 2 == 0 ? (ret1 + ret2) / 2 : ret1;
}
};
具体实现参考:hot100------第九周
数组第 k 个最大值
解法:小堆
使用优先级的小堆的方式来实现,数组依次如堆:当堆的个数大于k 时,出堆顶...遍历完成后此时堆顶就是答案
cpp
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int,vector<int>,greater<int>> q;
for(auto& num: nums)
{
q.push(num);
if(q.size() > k)
{
q.pop();
}
}
return q.top();
}
};
以上便是全部内容,有问题欢迎在评论区指正,感谢观看!