本人博客
https://www.liyunxu.work/
二分专题
sort函数的使用
包含在算法头文件中(#include )
- 对于内置数据类型 可以直接排序 比如
int num[100];
sort(num, num + 100);
- 对于自定义数据类型 需要提供比较的方法 比如
struct node {
int x, y;
};
bool cmp(const node &a, const node &b) {
return a.x > b.x;
}
int main() {
node a[100];
sort(a, a + 100, cmp)
return 0;
}
二分答案
如果范围过大(超过int)在计算中间值的时候可以用l + (r - l ) / 2
对应OJ题目(#387,#390,#389,#393,#82,#391,#394,#392)
二分的两种特殊情况
- 前面一堆1后面一堆0(111111111000000000) 我们找最后一个1
C++版
while (l != r)
mid = (l + r + 1) / 2;
l = mid;
r = mid - 1;
Java版
while (left != right) {
mid = (right + left + 1) >> 1;
if (nums[mid] <= target) {
left = mid;
} else {
right = mid - 1;
}
}
- 前面一堆0后面一堆1(000000001111111111)我们找第一个1
C++版
while (l != r)
mid = (l + r) / 2;
l = mid + 1;
r = mid;
Java版
while (left != right) {
mid = left + ((right - left) >> 1);
if (nums[mid] >= target) {
right = mid;
} else {
left = mid + 1;
}
}
双指针
LeetCode977题<有序数组的平方>
因为给定数组是升序排列的,但是有负数,负数的平方会使其变大。所以不能直接平方返回。
用一根指针i只想数组头,一根指针j指向数组尾,然后比较nums[i]和nums[j]平方后的结果,逆序存储两者较大的值。
class Solution {
public int[] sortedSquares(int[] nums) {
int n = nums.length;
int ans[] = new int[n];
for (int i = 0, j = n - 1, pos = n - 1; i <= j; pos--) {
int ii = nums[i] * nums[i];
int jj = nums[j] * nums[j];
if (ii > jj) {
ans[pos] = ii;
i++;
} else {
ans[pos] = jj;
j--;
}
}
return ans;
}
}
快慢指针
LeetCode19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
题解:
由于我们需要找到倒数第 n 个节点,因此我们可以使用两个指针 first 和 second 同时对链表进行遍历,并且 first 比 second 超前 n 个节点。当 first 遍历到链表的末尾时,second 就恰好处于倒数第 n 个节点。
如果我们能够得到的是倒数第 n 个节点的前驱节点而不是倒数第 n 个节点的话,删除操作会更加方便。因此我们可以考虑在初始时将 second 指向哑节点,其余的操作步骤不变。这样一来,当 first 遍历到链表的末尾时,second 的下一个节点就是我们需要删除的节点。
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/solution/shan-chu-lian-biao-de-dao-shu-di-nge-jie-dian-b-61/
来源:力扣(LeetCode)
示例1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例2:
输入:head = [1], n = 1
输出:[]
示例3:
输入:head = [1,2], n = 1
输出:[1]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode first = head;
ListNode second = dummy;
for (int i = 0; i < n; ++i) {
first = first.next;
}
while (first != null) {
first = first.next;
second = second.next;
}
second.next = second.next.next;
ListNode ans = dummy.next;
return ans;
}
}
STL容器
string
头文件:#include <string>
方法 | 功能 | 返回值 |
---|---|---|
empty | 检查字符串是否为空 | 如果为空返回true |
size/length | 返回字符数 | 字符数 |
clear | 清除内容 | 无 |
insert | 插入字符 | 指向首个被插入字符的迭代器 |
erase | 移除指定字符 | 指向擦除的后一个字符的迭代器,若不存在则返回end() |
substr | 返回从pos到pos+count的字符串 | 字符串 |
copy | 拷贝字符串 | 拷贝的字符个数 |
swap | 交换字符串 | 无 |
find | 寻找首个等于给定字符串的子串 | 找到的子串的首字符位置,或若找不到这种子串则为 npos |
vector
头文件#include <vector>
方法 | 功能 | 返回值 |
---|---|---|
empty | 检查容器是否为空 | 为空返回true |
size | 返回元素个数 | 元素个数 |
clear | 清除内容 | 无 |
erase | 删除指定位置的元素 | 返回移除的后一个元素的迭代器 |
push_back | 向末尾添加元素 | 无 |
pop_back | 移除末尾元素 | 无 |
stack
头文件#include <stact>
封装于双端队列
方法 | 功能 | 返回值 |
---|---|---|
top | 访问栈顶元素 | 栈顶元素 |
empty | 检查容器是否为空 | 为空返回true |
size | 返回元素个数 | 元素个数 |
push | 向栈顶插入元素 | 无 |
pop | 删除栈顶元素 | 无 |
queue
头文件#include <queue>
封装于双端队列
方法 | 功能 | 返回值 |
---|---|---|
front | 访问第一个元素 | 到首元素的引用 |
back | 访问最后一个元素 | 到末元素的引用 |
empty | 检查容器是否为空 | 为空返回true |
size | 返回元素个数 | 元素个数 |
push | 向队列尾部插入元素 | 无 |
pop | 删除首个元素 | 无 |
deque
头文件#include <deque>
方法 | 功能 | 返回值 |
---|---|---|
front | 访问第一个元素 | 到首元素的引用 |
back | 访问最后一个元素 | 到末元素的引用 |
empty | 检查容器是否为空 | 为空返回true |
size | 返回元素个数 | 元素个数 |
clear | 清除内容 | 无 |
push_back | 将元素添加到容器末尾 | 无 |
pop_back | 移除末尾元素 | 无 |
push_front | 插入元素到容器起始 | 无 |
pop_front | 移除首元素 | 无 |
priority_queue
头文件#include <queue>
默认是大顶堆,首个元素是最大的
方法 | 功能 | 返回值 |
---|---|---|
top | 访问栈顶元素 | 到顶元素的引用 |
empty | 检查容器是否为空 | 为空返回true |
size | 返回元素个数 | 元素个数 |
push | 插入元素,并对底层容器排序 | 无 |
pop | 删除队首元素 | 无 |
set
头文件#include <set>
方法 | 功能 | 返回值 |
---|---|---|
empty | 检查容器是否为空 | 为空返回true |
size | 返回元素个数 | 元素个数 |
clear | 清除内容 | 无 |
insert | 插入元素或结点 | 返回由指向被插入元素 |
count | 返回匹配特定键的元素数量 | 拥有比较等价于 key 或 x 的关键的元素数1或0 |
map
头文件#include <map>
重载了方括号运算符
方法 | 功能 | 返回值 |
---|---|---|
empty | 检查容器是否为空 | 为空返回true |
size | 返回元素个数 | 元素个数 |
clear | 清除内容 | 无 |
insert | 插入元素或结点 | 返回由指向被插入元素 |
count | 返回匹配特定键的元素数量 | 拥有比较等价于 key 或 x 的关键的元素数1或0 |
multiset
头文件#include <set>
不会去重可以有多个key,使用方法和set基本一致
multimap
头文件#include <map>
不会去重可以有多个key,使用方法和map基本一致,但是不可以使用方括号
unordered_set
头文件#include <unordered_set>
可以去重但是顺序是随机的
unordered_map
头文件#include <unordered_map>
可以去重但是顺序是随机的
unordered_multiset
头文件#include <unordered_set>
不去重顺序也是随机的
unordered_multimap
头文件#include <unordered_map>
不去重顺序也是是随机的
滑动窗口法
定长的滑动窗口法
eg : leetcode #143
给定 n 个整数,找出平均数最大且长度为 k 的连续子数组,并输出该最大平均数。
每次进来一个新的我们就把第一个删除
ass Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
double ans = INT_MIN, sum = 0;
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
if (i - k >= 0) {
sum -= nums[i - k];
}
if (i + 1 >= k) {
ans = max(ans, sum / k);
}
}
return ans;
}
};
树状数组
我们先来看前缀和数组
如果有一个数组a[i]那么他的前缀和数组s[i]就是对原数组的前i项求和,前缀和数组主要是是用来解决区间求和问题的,那么他的初始化时间复杂度是O(n),计算区间和的时间复杂度O(1),对原数组单点修改的时间复杂度为O(n)
那么如何解决单点修改慢的问题呢?
树状数组
要看树状数组我们先看lowbit()函数,功能就是一个整数的二进制的最低位的1对应十进制的值就是lowbit()函数的结果
那么树状数组c[i]就是前lowbit(i)项的和
例如 lowbit(10) = 2, C[10] = a[10]+a[9]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ymo03C10-1661763264440)(https://myblog2-0.oss-cn-beijing.aliyuncs.com/2022-08-29/3412f264-6e44-4794-8033-7a9df8b3a2d7_截图_选择区域_20210716193651.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cmw8ePUa-1661763264441)(https://myblog2-0.oss-cn-beijing.aliyuncs.com/2022-08-29/bfe9b449-8637-48b1-898f-629ab709ee29_截图_选择区域_20210716193957.png)]
我们对比两张图可以看出树状数组假如我们修改了a[1]的值那么我们修改的效率就会提高
基本操作
- 前缀和查询
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p2mWE8O4-1661763264442)(https://myblog2-0.oss-cn-beijing.aliyuncs.com/2022-08-29/b1aa39e6-a8dd-46a4-883d-2f96f4f8a9e2_截图_选择区域_20210716194545.png)]
s[i]可以拆解为几项c[i]取决与i的二进制1的个数 那么时间复杂度就是log(i)的
- 单点修改
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bknm4zmE-1661763264442)(https://myblog2-0.oss-cn-beijing.aliyuncs.com/2022-08-29/2bc1f519-a10d-477f-8faa-cde7c2033d78_截图_选择区域_20210716195919.png)]
那么如何取得c[5],c[6],c[8]呢?
我们只需要5 + lowbit(5) = 6. 6 + lowbit(6) = 8 就可以取得。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SjD0pa0P-1661763264443)(https://myblog2-0.oss-cn-beijing.aliyuncs.com/2022-08-29/cca98777-20a6-40b8-8358-e75e35942721_截图_选择区域_20210716200136.png)]
递推问题
LeetCode53最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
class Solution {
public int maxSubArray(int[] nums) {
int ans = nums[0];
int sum = nums[0];
for (int i = 1; i < nums.length; i++) {
sum = Integer.max(nums[i], nums[i] + sum);
ans = Integer.max(ans, sum);
}
return ans;
}
}
解释:sum看成是一个到第i项为止最大的连续子序列和数组,因为只需要第i项的前一项,所以就用一个sum变量维护就可以,sum更新有两种情况
- 要么是本身nums[i]自己大。
- 要么是nums[i] + sum大也就是加上到前一项为止的最大连续子序列和。
之后更新ans。
数组判断重复值或着快速查值问题
LeetCode 36. 有效的数独
判断一个 9x9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
示例1:
输入:board =
[["5","3",".",".","7",".",".",".","."]
,["6",".",".","1","9","5",".",".","."]
,[".","9","8",".",".",".",".","6","."]
,["8",".",".",".","6",".",".",".","3"]
,["4",".",".","8",".","3",".",".","1"]
,["7",".",".",".","2",".",".",".","6"]
,[".","6",".",".",".",".","2","8","."]
,[".",".",".","4","1","9",".",".","5"]
,[".",".",".",".","8",".",".","7","9"]]
输出:true
题目让我们找行不能重复,列不能重复,每一个33的表格不能重复,所以可以建立3个哈希表(row, columns, boxIndex),分别存储行,列,和每一个33的块区域。kay是出现的数字,value则是对应出现数字的次数。
class Solution {
public boolean isValidSudoku(char[][] board) {
HashMap<Character, Integer>[] row = new HashMap[9];
HashMap<Character, Integer>[] columns = new HashMap[9];
HashMap<Character, Integer>[] boxIndex = new HashMap[9];
for (int i = 0; i < 9; i++) {
row[i] = new HashMap<Character, Integer>();
columns[i] = new HashMap<Character, Integer>();
boxIndex[i] = new HashMap<Character, Integer>();
}
int count = 0;
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (Character.compare(board[i][j], '.') == 0) continue;
count = row[i].getOrDefault(board[i][j], 0) + 1;
row[i].put(board[i][j], count);
count = columns[j].getOrDefault(board[i][j], 0) + 1;
columns[j].put(board[i][j], count);
count = boxIndex[(i / 3) * 3 + j / 3].getOrDefault(board[i][j], 0) + 1;
boxIndex[(i / 3) * 3 + j / 3].put(board[i][j], count);
if (row[i].get(board[i][j]) > 1 || columns[j].get(board[i][j]) > 1 || boxIndex[(i / 3) * 3 + j / 3].get(board[i][j]) > 1) {
return false;
}
}
}
return true;
}
}