思想:分治
流程:
心得:有关边界问题建议背一个模板
模板:
// 使用 do-while
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[(l + r) >> 1]; // 轴点选择为中心元素
while (i < j)
{
do i ++ ; while (q[i] < x); // *
do j -- ; while (q[j] > x); // *
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j);
quick_sort(q, j + 1, r);
}
// 使用 while
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = a[l + r >> 1];
while (i < j)
{
while (q[++i] < x) {} // *
while (q[--j] > x) {} // *
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j);
quick_sort(q, j + 1, r);
}
#include
using namespace std;
const int N = 100010;
int a[N];
void quick_sort(int l, int r) {
if (l >= r) return;
int i = l - 1, j = r + 1, x = a[(l + r) >> 1];
while (i < j) {
do i++; while (a[i] < x);
do j--; while (a[j] > x);
if (i < j) swap(a[i], a[j]);
}
quick_sort(l, j);
quick_sort(j + 1, r);
}
int main()
{
int n; cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
quick_sort(0, n - 1);
for (int i = 0; i < n; i++) cout << a[i] << " ";
return 0;
}
Leetcode:912. 排序数组 - 力扣(LeetCode)
思想:分治
流程:
模板:
void merge_sort(int q[], int l, int r) {
if (l >= r ) return;
// 确定分界点
int mid = (l + r) >> 1;
// 递归排序左右两部分
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
// 归并:合二为一
int k = 0, i = l, j = mid + 1;
// 满足条件的情况下,将小的元素放入 tmp 中
while (i <= mid && j <= r)
tmp[k++] = (q[i] <= q[j]) ? q[i++] : q[j++];
// 将剩余元素放到入 tmp
while (i <= mid) tmp[k++] = q[i++];
while (j <= r) tmp[k++] = q[j++];
// 将 tmp 中的元素拷贝到 q
for (i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];
}
#include
using namespace std;
const int N = 1e5 + 10;
int a[N], tmp[N];
void merge_sort(int l, int r)
{
if (l >= r) return;
int mid = (l + r) >> 1;
// 递归左右两部分
merge_sort(l, mid);
merge_sort(mid + 1, r);
// 归并
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
tmp[k++] = (a[i] <= a[j]) ? a[i++] : a[j++];
while (i <= mid) tmp[k++] = a[i++];
while (j <= r) tmp[k++] = a[j++];
for (i = l, j = 0; i <= r; i++, j++) a[i] = tmp[j];
}
int main()
{
int n; cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
merge_sort(0, n - 1);
for (int i = 0; i < n; i++) cout << a[i] << " ";
return 0;
}
class Solution {
public int[] sortArray(int[] nums) {
mergeSort(nums, 0, nums.length - 1);
return nums;
}
void mergeSort(int[] q, int l, int r) {
if (l >= r) return;
int mid = (l + r) >> 1;
// 递归排序左右两部分
mergeSort(q, l, mid);
mergeSort(q, mid + 1, r);
// 归并
int[] tmp = new int[r - l + 1]; // 开太大的数组会超时
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
tmp[k++] = (q[i] <= q[j]) ? q[i++] : q[j++];
while (i <= mid) tmp[k++] = q[i++];
while (j <= r) tmp[k++] = q[j++];
for (i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j]; // 赋值
}
}
二分的本质:找到一个性质,可以将数据一分为二,一边满足一边不满足
有序一定可以用二分,不有序不一定不可以用二分
二分每次都会保证新的区间中一定会包含答案(处理边界问题)
模板:
bool check(int x) { /* ... */ } // 检查 x 是否满足某种性质
// 区间 [l, r] 被划分成 [l, mid] 和 [mid + 1, r] 时使用
int bsearch_1(int l, int r) {
while (l < r) {
int mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
if (check(l)) return l;
return -1;
}
// 区间 [l, r] 被划分成 [l, mid - 1] 和 [mid, r] 时使用
int bsearch_2(int l, int r) {
while (l < r) {
int mid = (l + r + 1) >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
import java.util.*;
class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int q = sc.nextInt();
int[] arr = new int[n];
for (int i = 0; i < n; i++)
arr[i] = sc.nextInt();
while (q-- > 0) {
int x = sc.nextInt();
int l = 0, r = n - 1;
while (l < r) {
int mid = (l + r) >> 1;
if (arr[mid] >= x) r = mid;
else l = mid + 1;
}
if (arr[l] != x) System.out.println("-1 -1");
else {
System.out.print(l + " ");
l = 0; r = n - 1;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (arr[mid] <= x) l = mid;
else r = mid - 1;
}
System.out.print(r + "\n");
}
}
}
}
浮点数二分比较简单,不需要考虑太多边界问题
模板:
bool check(double x) {/* ... */} // 检查 x 是否满足某种性质
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps) {
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
import java.util.*;
class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double n = sc.nextDouble();
double l = -10000, r = 10000;
while (r - l > 1e-8) {
double mid = (l + r) / 2;
if (mid * mid * mid <= n) l = mid;
else r = mid;
}
System.out.println(String.format("%.6f", l));
}
}
模板:
// C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A, vector<int> &B) {
if (A.size() < B.size()) return add(B, A);
vector<int> C;
int t = 0; // 进位
for (int i = 0; i < A.size(); i++) { // 根据长的来加
t += A[i];
if (i < B.size()) t += B[i];
c.push_back(t % 10);
t /= 10;
}
if (t) C.push_back(t);
return C;
}
Java 有大数类,可以直接用来进行大数运算
import java.math.BigInteger;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException{
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
BigInteger a = new BigInteger(reader.readLine());
BigInteger b = new BigInteger(reader.readLine());
System.out.println(a.add(b));
reader.close();
}
}
不使用大数类的做法:
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String a = sc.next();
String b = sc.next();
System.out.println(add(a, b));
}
public static String add(String a, String b) {
if (a.length() < b.length()) return add(b, a);
StringBuffer sb = new StringBuffer();
int t = 0; // 进位
for (int i = a.length() - 1, j = b.length() - 1; i >= 0 || j >= 0; i--, j--) {
int n1 = i >= 0 ? a.charAt(i) - '0' : 0;
int n2 = j >= 0 ? b.charAt(j) - '0' : 0;
t += n1 + n2;
sb.append(t % 10);
t /= 10;
}
if (t > 0) sb.append(t);
return sb.reverse().toString();
}
}
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in) ;
String a = sc.next();
String b = sc.next();
List<Integer> res = add(a, b);
for (int i = res.size() - 1; i >= 0; i--)
System.out.print(res.get(i));
}
public static List<Integer> add(String a, String b) {
List<Integer> temp = new ArrayList<>();
int t = 0; // 进位
for (int i = a.length() - 1, j = b.length() - 1;; i >= 0 || j >= 0; i--, j--) {
int n1 = i >= 0 ? a.charAt(i) - '0' : 0;
int n2 = j >= 0 ? b.charAt(j) - '0' : 0;
t += n1 + n2;
temp.add(t % 10);
t /= 10;
}
if (t > 0) temp.add(t);
return temp;
}
}
模板:
// C = A - B, 满足 A >= B, A >= 0, B >= 0
vector<int> sub(vector<int> &A, vsctor<int> &B) {
vector<int> C;
int t = 0;
for (int i = 0; i < A.size(); i++) {
t = A[i] - t;
if (i < B.size()) t -= B[i];
C.push_back((t + 10) % 10);
t = (t < 0) ? 1 : 0;
}
// 去掉前置的 0
while (C.size() > 1 && C.back() == 0) c.pop_back();
return C;
}
import java.util.Scanner;
public class Main{
public static void main(String[] args){
String a, b;
Scanner sc = new Scanner(System.in);
a = sc.next();
b = sc.next();
char[] A = new char[a.length()], B = new char[b.length()];
for (int i = a.length() - 1; i >= 0; i --) A[a.length() - 1 - i] = a.charAt(i);
for (int i = b.length() - 1; i >= 0; i --) B[b.length() - 1 - i] = b.charAt(i);
if (cmp(A, B)) {
String c = sub(A, B);
System.out.println(c);
}else{
String c = sub(B, A);
System.out.println("-" + c);
}
}
public static String sub(char[] A, char[] B){
StringBuffer sb = new StringBuffer();
int t = 0;
for (int i = 0; i < A.length; i ++) {
t = A[i] - '0' - t; // 剪去借位
if (i < B.length) t -= B[i] - '0';
sb.append((t + 10) % 10);
t = (t < 0) ? 1 : 0;
}
String s = sb.reverse().toString();
// 截去开头的 0
int i = 0;
for (; i < s.length() - 1; i ++)
if (s.charAt(i) != '0') break;
return s.substring(i, s.length());
}
public static boolean cmp(char[] A, char[] B) {
if (A.length != B.length) return A.length > B.length;
// 相等从高位进行比较
for (int i = A.length - 1; i >= 0; i --)
if(A[i] != B[i]) return A[i] > B[i];
return true;
}
}
模板:高精度 乘 低精度
// C = A * b, A >= 0, b >= 0
vector<int> mul(vector<int> &A, int b)
{
vector<int> C;
int t = 0;
for (int i = 0; i < A.size() || t; i ++ )
{
if (i < A.size()) t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String a = sc.nextLine();
int b = sc.nextInt();
// 逆序存放
char[] A = new char[a.length()];
for (int i = a.length() - 1; i >= 0; i--)
A[a.length() - i - 1] = a.charAt(i);
System.out.println(mul(A, b));
}
public static String mul(char[] A, int b) {
int t = 0; // 进位
StringBuffer sb = new StringBuffer();
for (int i = 0; i < A.length || t != 0; i++) {
if (i < A.length) t += (A[i] - '0') * b;
sb.append(t % 10);
t /= 10;
}
String s = sb.reverse().toString();
// 截去前面的 0
int i = 0;
for (i = 0; i < A.length - 1; i++)
if (s.charAt(i) != '0') break;
return s.substring(i);
}
}
模板:
S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]
前缀和下标从 1 开始,方便操作
import java.util.*;
class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
// 多开一位方便操作
int[] arr = new int[n + 1]; // arr[0] = 1
int[] S = new int[n + 1]; // S[0] = 0
for (int i = 1; i <= n; i++) {
arr[i] = sc.nextInt();
S[i] = S[i - 1] + arr[i];
}
while (m-- > 0) {
int l = sc.nextInt();
int r = sc.nextInt();
System.out.println(S[r] - S[l - 1]);
}
}
}
模板:
S[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
求 S[i, j] :
S[i, j] = S[i - 1, j] + S[i, j - 1] - S[i - 1][j - 1] + a[i][j]
求 (x1, y1), (x2, y2) 子矩阵中的和:
f(x1, y1, x2, y2) = S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
import java.util.*;
public class Main {
public static void main(String[] args) {
// 输入值进行初始化
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int q = in.nextInt();
// 初始化 arr
int[][] arr = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
arr[i][j] = in.nextInt();
// 求解 s[i][j]
int[][] s = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + arr[i][j];
// 循环输出
while (q-- > 0) {
// 定位获取区间大小
int x1 = in.nextInt();
int y1 = in.nextInt();
int x2 = in.nextInt();
int y2 = in.nextInt();
// 计算, 结合图来理解
int res = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
System.out.println(res);
}
}
}
模板:
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
模板题:799. 最长连续不重复子序列 - AcWing题库
#include
using namespace std;
const int N = 100010;
int n;
int a[N], s[N];
int main()
{
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
int res = 0;
for (int i = 0, j = 0; i < n; i++)
{
s[a[i]]++;
while (s[a[i]] > 1)
{
s[a[j]]--;
j++;
}
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}
本题用 哈希、二分 也可以做,核心关注 双指针 的思路
#include
using namespace std;
const int N = 100010;
int A[N], B[N];
int main()
{
int n, m, x;
cin >> n >> m >> x;
for (int i = 0; i < n; i++) cin >> A[i];
for (int i = 0; i < m; i++) cin >> B[i];
for (int i = 0, j = m - 1; A[i] < x; i++)
{
while (A[i] + B[j] > x) j--;
if (A[i] + B[j] == x)
{
cout << i << " " << j;
return 0;
}
}
return 0;
}
#include
using namespace std;
const int N = 100010;
int a[N], b[N];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < m; i++) cin >> b[i];
int i = 0;
for (int j = 0; j < m; j++)
{
if (i < n && a[i] == b[j]) i++;
}
if (i == n) cout << "Yes" << endl;
else cout << "No" << endl;
return 0;
}
原码、反码、补码:
x = 1010
原码 x :0....01010
反码 -x :1....10101
补码 -x + 1 :1....10110
模板:
求 n 的第 k 位数字: n >> k & 1
返回 n 的最后一位 1:lowbit(n) = n & -n
#include
using namespace std;
// 返回末尾的 1
// 11000 ---> 1000
// 1010 ---> 10
int lowbit(int x)
{
return x & -x;
}
int main()
{
int n; cin >> n;
while (n --) {
int x;
cin >> x;
int res = 0;
// 写法 1
while (x) x -= lowbit(x), res++;
// 写法 2
// while (x) x &= (x - 1), res++;
cout << res << " ";
}
return 0;
}
模板:
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// 二分求出 x 对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1; // 映射到1, 2, ...n
}
离散化的本质,是映射,将间隔很大的点,映射到相邻的数组元素中。减少对空间的需求,也减少计算量。
import java.util.*;
class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = 300010; // 坐标 x 的上限为 1e5,两个坐标 l, r 的上限也为 1e5, 所以需要 3*1e5
int[] a = new int[N]; // 离散量
int[] s = new int[N]; // 前缀和
// 将所有用到的数存到 alls 中 (包含 x, l, r),真实下标和映射下标的关系
// 其中会有先后顺序的差别以及重复,需要排序及去重
List<Integer> alls = new ArrayList<>();
List<Pairs> add = new ArrayList<>(); // n 次操作
List<Pairs> query = new ArrayList<>(); // m 次询问
int n = sc.nextInt();
int m = sc.nextInt();
// n 次操作
for (int i = 0; i < n; i++) {
int x = sc.nextInt();
int c = sc.nextInt();
add.add(new Pairs(x, c));
alls.add(x); // 存入下标,统一离散化
}
// m 次询问
for (int i = 0; i < m; i++) {
int l = sc.nextInt();
int r = sc.nextInt();
query.add(new Pairs(l, r));
// 存入左右端点
alls.add(l);
alls.add(r);
}
// 到此为止,alls 中存好了所有会被用到的数轴上的点,可以进行离散化操作
Collections.sort(alls); // 1. 排序
int unique = unique (alls); // 2. 去重
alls = alls.subList(0, unique); // 保存去重后的 List
// 离散化操作(做映射)
for (Pairs item : add) {
int index = find(item.first, alls);
a[index] += item.second;
}
// 求前缀和
for (int i = 1; i <= alls.size(); i++)
s[i] = s[i - 1] + a[i];
// 输出结果
for (Pairs item: query) {
int l = find(item.first, alls);
int r = find(item.second, alls);
System.out.println(s[r] - s[l - 1]);
}
}
// 去重
static int unique(List<Integer> list) {
int j = 0;
for (int i = 0; i < list.size(); i++) {
if (i == 0 || list.get(i) != list.get(i - 1)) {
list.set(j, list.get(i));
j++;
}
}
return j;
}
// 二分搜索,查找 List 中 x 的位置
static int find(int x, List<Integer> list) {
int l = 0, r = list.size() - 1;
while (l < r) {
int mid = (l + r) >> 1;
if (list.get(mid) >= x) r = mid;
else l = mid + 1;
}
return l + 1; // 考虑到前缀和
}
}
class Pairs {
int first;
int second;
public Pairs(int first, int second) {
this.first = first;
this.second = second;
}
}
模板:
// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
vector<PII> res;
sort(segs.begin(), segs.end());
int st = -2e9, ed = -2e9;
for (auto seg : segs)
if (ed < seg.first)
{
if (st != -2e9) res.push_back({st, ed});
st = seg.first, ed = seg.second;
}
else ed = max(ed, seg.second);
if (st != -2e9) res.push_back({st, ed});
segs = res;
}
import java.util.*;
public class Main {
public static void main(String[] args){
int N = 100010;
ArrayList<int[]> list = new ArrayList();
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
for (int i = 0; i < n; i++) {
int[] pair = new int[2];
pair[0] = sc.nextInt();
pair[1] = sc.nextInt();
list.add(pair);
}
// 根据区间的开始位置排序
list.sort((o1, o2) -> o1[0] - o2[0]);
int k = 0, r = Integer.MIN_VALUE;
for (int[] a : list) {
if (a[0] > r) k++;
r = Math.max(r, a[1]);
}
System.out.println(k);
}
}