interview
剑指offer
题库链接: https://leetcode.cn/problem-list/e8X3pBZi/
整数除法
剑指 Offer II 001. 整数除法
给定整数a、b,求得商的结果
条件:
1、不能使用除法、取余数等运算
2、只能存储32位有符号整数(范围是[-2^31,2^31-1]),如果溢出了就返回2^31-1
解法
- 处理边界条件,当a是最小值,判断b是1或者-1;当b是最小值,a是最小值则为1,其他都为0
- 使用二分法+快速乘来判断是否满足
#include <iostream>
#include <limits>
using namespace std;
// 快速乘
// x 和 y 是负数,z 是正数
// 判断 z * y >= x 是否成立
bool quickAdd(int y, int z, int x) {
int result = 0, add = y;
while (z > 0) { // 不能使用除法
if (z & 1) {
// 需要保证 result + add >= x
if (result < x - add) {
return false;
}
result += add;
}
if (z != 1) {
// 需要保证 add + add >= x
if (add < x - add) {
return false;
}
add += add;
}
z >>= 1;
}
return true;
}
int divide(int a, int b) {
if (a == numeric_limits<int>::min()) { // 考虑被除数为最小值的情况
if (b == 1) {
return numeric_limits<int>::min();
}
if (b == -1) {
return numeric_limits<int>::max();
}
}
if (b == numeric_limits<int>::min()) { // 考虑除数为最小值的情况
if (a == numeric_limits<int>::min()) {
return 1;
}
return 0;
}
if (a == 0) { // 考虑被除数为 0 的情况
return 0;
}
// 一般情况,使用二分查找
// 将所有的正数取相反数,这样就只需要考虑一种情况
bool rev = false;
if (a > 0) {
a = -a;
rev = !rev;
}
if (b > 0) {
b = -b;
rev = !rev;
}
int ans = 0;
int left = 1, right = numeric_limits<int>::max();
while (left <= right) {
int mid = left + ((right - left) >> 1); // 注意溢出,并且不能使用除法
if (quickAdd(b, mid, a)) {
ans = mid;
if (mid == numeric_limits<int>::max()) { // 注意溢出
break;
}
left = mid + 1;
} else {
right = mid - 1;
}
}
return rev ? -ans : ans;
}
int main() {
std::cout << divide(4, 3) << std::endl;
return 0;
}
package main
import "math"
// 快速乘
// x 和 y 是负数,z 是正数
// 判断 z * y >= x 是否成立
func QuickAdd(y, z, x int) bool {
for result, add := 0, y; z > 0; z >>= 1 { // 不能使用除法
if z&1 > 0 {
// 需要保证 result + add >= x
if result < x-add {
return false
}
result += add
}
if z != 1 {
// 需要保证 add + add >= x
if add < x-add {
return false
}
add += add
}
}
return true
}
func divide(a, b int) int {
if a == math.MinInt32 { // 考虑被除数为最小值的情况
if b == 1 {
return math.MinInt32
}
if b == -1 {
return math.MaxInt32
}
}
if b == math.MinInt32 { // 考虑除数为最小值的情况
if a == math.MinInt32 {
return 1
}
return 0
}
if a == 0 { // 考虑被除数为 0 的情况
return 0
}
// 一般情况,使用二分查找
// 将所有的正数取相反数,这样就只需要考虑一种情况
rev := false
if a > 0 {
a = -a
rev = !rev
}
if b > 0 {
b = -b
rev = !rev
}
ans := 0
left, right := 1, math.MaxInt32
for left <= right {
mid := left + (right-left)>>1 // 注意溢出,并且不能使用除法
if QuickAdd(b, mid, a) {
ans = mid
if mid == math.MaxInt32 { // 注意溢出
break
}
left = mid + 1
} else {
right = mid - 1
}
}
if rev {
return -ans
}
return ans
}
2、
剑指 Offer II 002. 二进制加法
给定两个01字符串,求得加法字符串
解法
- 相对应下标(len-i-1)不断相加,计算商继续作为carry、模作为当前下标的值。
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
// 二进制字符串相加
string addBinary(string a, string b) {
string ans = "";
int carry = 0;
int lenA = a.size(), lenB = b.size();
int n = max(lenA, lenB);
// 从后往前逐位相加
for (int i = 0; i < n; i++) {
if (i < lenA) {
carry += a[lenA - i - 1] - '0';
}
if (i < lenB) {
carry += b[lenB - i - 1] - '0';
}
ans = char(carry % 2 + '0') + ans;
carry /= 2;
}
// 如果最后有进位,补上一个1
if (carry > 0) {
ans = '1' + ans;
}
return ans;
}
int main() {
std::cout << addBinary("11", "1") << std::endl;
std::cout << addBinary("11", "1001") << std::endl;
return 0;
}
package main
import "strconv"
func AddBinary(a string, b string) string {
ans := ""
carry := 0
lenA, lenB := len(a), len(b)
n := max(lenA, lenB)
for i := 0; i < n; i++ {
if i < lenA {
carry += int(a[lenA-i-1] - '0')
}
if i < lenB {
carry += int(b[lenB-i-1] - '0')
}
ans = strconv.Itoa(carry%2) + ans
carry /= 2
}
if carry > 0 {
ans = "1" + ans
}
return ans
}
func max(x, y int) int {
if x > y {
return x
}
return y
}
3、
剑指 Offer II 003. 前 n 个数字二进制中 1 的个数
给定一个数字n,从[0,n]中计算出二进制表示中1的个数
解法
- 计算一个数的二进制表示中有多少个1,
x>0
的循环条件中,能有多少次x&=x-1
满足条件
#include <iostream>
#include <vector>
using namespace std;
// 计算一个整数的二进制表示中1的个数
int onesCount(int x) {
int ones = 0;
while (x > 0) {
x &= (x - 1); // 消除最低位的1
ones++;
}
return ones;
}
// 返回从0到n的每个整数的二进制表示中1的个数
vector<int> countBits(int n) {
vector<int> bits(n + 1);
for (int i = 0; i <= n; ++i) {
bits[i] = onesCount(i);
}
return bits;
}
int main() {
vector<int> bits = countBits(5);
for (int i = 0; i < bits.size(); ++i) {
cout << bits[i] << " ";
}
cout << endl;
return 0;
}
package main
func onesCount(x int) (ones int) {
for ; x > 0; x &= x - 1 {
ones++
}
return
}
func CountBits(n int) []int {
bits := make([]int, n+1)
for i := range bits {
bits[i] = onesCount(i)
}
return bits
}
4、
剑指 Offer II 004. 只出现一次的数字
给定一个整数数组,其中一个元素出现了一次,其他元素出现了三次,找到那个只出现了一次的元素
要求: O(n)
时间复杂度, O(1)
空间复杂度
解法
- 将数组中的数字看成二进制格式,对于出现了三次的数字,他们对应的比特位数字和为0或者3,对于出现一次的数字,对应的比特位数字和为1或者4,将每一位的和取余所得到的余数就是结果对应的元素对应的位置。
#include <iostream>
#include <vector>
using namespace std;
int singleNumber(const vector<int>& nums) {
int ans = 0; // 32位整数,保存结果
for (int i = 0; i < 32; ++i) {
int total = 0; // 记录第i位的1的个数
for (int num : nums) {
total += (num >> i) & 1; // 取出num的第i位并累加
}
if (total % 3 > 0) { // 如果该位上的1的个数不是3的倍数
ans |= (1 << i); // 将该位设置为1
}
}
return ans;
}
int main() {
vector<int> nums = { 1,1,1,2,2,2,3,3,3,4 };
int ans = singleNumber(nums);
cout << ans << endl;
return 0;
}
package main
func SingleNumber(nums []int) int {
ans := int32(0)
for i := 0; i < 32; i++ {
total := int32(0)
for _, num := range nums {
total += int32(num) >> i & 1
}
if total%3 > 0 {
ans |= 1 << i
}
}
return int(ans)
}
5、
剑指 Offer II 005. 单词长度的最大乘积
一个字符串数组,寻找满足 不包含相同字符 的两个字符串的长度乘积,找到满足条件的最大长度
解法
- 将字符串转为int类型mask来表示哪些字母是出现了的
- 使用位运算来判断是否存在相同的字符
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int maxProduct(const vector<string> &words) {
int ans = 0;
int n = words.size();
vector<int> masks(n, 0); // 用于存储每个单词的掩码
// 计算每个单词的字母掩码
for (int i = 0; i < n; ++i) {
for (char ch : words[i]) {
masks[i] |= (1 << (ch - 'a')); // 将字母对应的位标记为1
}
}
// 两两比较掩码,找出没有公共字母的单词组合,并计算其乘积
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if ((masks[i] & masks[j]) == 0) { // 判断是否没有公共字母
int product = words[i].size() * words[j].size();
if (product > ans) {
ans = product;
}
}
}
}
return ans;
}
int main() {
vector<string> words = {"abcw", "baz", "foo", "bar", "xtfn", "abcdef"};
int ans = maxProduct(words);
cout << ans << endl;
return 0;
}
package main
func MaxProduct(words []string) (ans int) {
masks := make([]int, len(words))
for i, word := range words {
for _, ch := range word {
masks[i] |= 1 << (ch - 'a')
}
}
for i, x := range masks {
for j, y := range masks[:i] {
if x&y == 0 && len(words[i])*len(words[j]) > ans {
ans = len(words[i]) * len(words[j])
}
}
}
return
}
6、
剑指 Offer II 006. 排序数组中两个数字之和
一个升序数组,找到其中的两个元素和为目标值,返回其目标下标。同一个数字不能使用两次,并且题目要求一定会存在满足条件的答案。
#include <vector>
std::vector<int> TwoSum(const std::vector<int> &numbers, int target) {
int low = 0, high = numbers.size() - 1;
while (low < high) {
int sum = numbers[low] + numbers[high];
if (sum == target) {
return {low, high};
} else if (sum < target) {
++low;
} else {
--high;
}
}
return {-1, -1};
}
package main
func TwoSum(numbers []int, target int) []int {
low, high := 0, len(numbers)-1
for low < high {
sum := numbers[low] + numbers[high]
if sum == target {
return []int{low, high}
} else if sum < target {
low++
} else {
high--
}
}
return []int{-1, -1}
}
7、
剑指 Offer II 007. 数组中和为 0 的三个数
给定一个整数数组,返回其中和为0的三元组,并且需要答案不能重复
解法
- 先进行排序,优化搜索条件
- 记得跳过相同的值,即相同的一段,只计算第一个进行剪枝。
#include <algorithm>
#include <vector>
std::vector<std::vector<int>> ThreeSum(std::vector<int> &nums) {
std::vector<std::vector<int>> ans;
std::sort(nums.begin(), nums.end());
int n = nums.size();
for (int first = 0; first < n; ++first) {
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
int third = n - 1;
int target = -nums[first];
for (int second = first + 1; second < n; ++second) {
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
while (second < third && nums[second] + nums[third] > target) {
--third;
}
if (second == third) {
break;
}
if (nums[second] + nums[third] == target) {
ans.push_back({nums[first], nums[second], nums[third]});
}
}
}
return ans;
}
package main
import "sort"
func ThreeSum(nums []int) [][]int {
n := len(nums)
sort.Ints(nums)
ans := make([][]int, 0)
// 枚举 a
for first := 0; first < n; first++ {
// 需要和上一次枚举的数不相同
if first > 0 && nums[first] == nums[first-1] {
continue
}
// c 对应的指针初始指向数组的最右端
third := n - 1
target := -1 * nums[first]
// 枚举 b
for second := first + 1; second < n; second++ {
// 需要和上一次枚举的数不相同
if second > first+1 && nums[second] == nums[second-1] {
continue
}
// 需要保证 b 的指针在 c 的指针的左侧
for second < third && nums[second]+nums[third] > target {
third--
}
// 如果指针重合,随着 b 后续的增加
// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
if second == third {
break
}
if nums[second]+nums[third] == target {
ans = append(ans, []int{nums[first], nums[second], nums[third]})
}
}
}
return ans
}
8、
剑指 Offer II 008. 和大于等于 target 的最短子数组
给定一个整数数组,寻找满足数组和大于target的一个最短连续子数组
解法
- 滑动窗口: 计算满足和大于目标值的最短子数组长度
#include <algorithm>
#include <climits>
#include <vector>
int minSubArrayLen(int s, const std::vector<int> &nums) {
int n = nums.size();
if (n == 0) {
return 0;
}
int ans = INT_MAX;
int start = 0, end = 0, sum = 0;
while (end < n) {
sum += nums[end];
while (sum >= s) {
ans = std::min(ans, end - start + 1);
sum -= nums[start];
++start;
}
++end;
}
return (ans == INT_MAX) ? 0 : ans;
}
package main
import "math"
func MinSubArrayLen(s int, nums []int) int {
n := len(nums)
if n == 0 {
return 0
}
ans := math.MaxInt32
start, end := 0, 0
sum := 0
for end < n {
sum += nums[end]
for sum >= s {
ans = min(ans, end-start+1)
sum -= nums[start]
start++
}
end++
}
if ans == math.MaxInt32 {
return 0
}
return ans
}
func min(x, y int) int {
if x < y {
return x
}
return y
}
9、
剑指 Offer II 009. 乘积小于 K 的子数组
给定一个数组,寻找其中满足数组乘积小于k的子数组的个数
#include <vector>
int numSubarrayProductLessThanK(const std::vector<int> &nums, int k) {
if (k <= 1)
return 0;
int prod = 1, ans = 0, i = 0;
for (int j = 0; j < nums.size(); ++j) {
prod *= nums[j];
while (prod >= k) {
prod /= nums[i++];
}
ans += j - i + 1;
}
return ans;
}
package main
func numSubarrayProductLessThanK(nums []int, k int) (ans int) {
prod, i := 1, 0
for j, num := range nums {
prod *= num
for ; i <= j && prod >= k; i++ {
prod /= nums[i]
}
ans += j - i + 1
}
return
}
10、
剑指 Offer II 010. 和为 k 的子数组
给定一个数组,寻找其中满足数组和为k的子数组的个数
解法
- 数组和在计算过程中需要记录下来,并且同时计算该数组和对应的个数
#include <unordered_map>
#include <vector>
int subarraySum(const std::vector<int> &nums, int k) {
int count = 0, pre = 0;
std::unordered_map<int, int> m;
m[0] = 1;
for (int i = 0; i < nums.size(); ++i) {
pre += nums[i];
if (m.find(pre - k) != m.end()) {
count += m[pre - k];
}
m[pre]++;
}
return count;
}
package main
func subarraySum(nums []int, k int) int {
count, pre := 0, 0
m := map[int]int{}
m[0] = 1
for i := 0; i < len(nums); i++ {
pre += nums[i]
if _, ok := m[pre-k]; ok {
count += m[pre-k]
}
m[pre] += 1
}
return count
}
11、
剑指 Offer II 011. 0 和 1 个数相同的子数组
给定一个只包含0和1的数组,找到其中含有相同0和1
数量的最大长度的子数组
解法
- 使用map记录某个前缀和最左边的下标是多少
#include <algorithm>
#include <unordered_map>
#include <vector>
int FindMaxLength(const std::vector<int> &nums) {
std::unordered_map<int, int> mp = {{0, -1}};
int maxLength = 0, counter = 0;
for (int i = 0; i < nums.size(); ++i) {
counter += (nums[i] == 1) ? 1 : -1;
if (mp.find(counter) != mp.end()) {
maxLength = std::max(maxLength, i - mp[counter]);
} else {
mp[counter] = i;
}
}
return maxLength;
}
package main
func FindMaxLength(nums []int) (maxLength int) {
mp := map[int]int{0: -1}
counter := 0
for i, num := range nums {
if num == 1 {
counter++
} else {
counter--
}
if prevIndex, has := mp[counter]; has {
maxLength = max(maxLength, i-prevIndex)
} else {
mp[counter] = i
}
}
return
}
12、
剑指 Offer II 012. 左右两边子数组的和相等
找到数组的最左边的中心下标,中心下标是指以该下标为分界,左右数组元素和相同的下标
解法
- 判断
2 * 前缀和 + 当前值
是否等于数组总和
#include <vector>
int pivotIndex(const std::vector<int> &nums) {
int total = 0;
for (int v : nums) {
total += v;
}
int sum = 0;
for (int i = 0; i < nums.size(); ++i) {
if (2 * sum + nums[i] == total) {
return i;
}
sum += nums[i];
}
return -1;
}
package main
func pivotIndex(nums []int) int {
total := 0
for _, v := range nums {
total += v
}
sum := 0
for i, v := range nums {
if 2*sum+v == total {
return i
}
sum += v
}
return -1
}
13、
剑指 Offer II 013. 二维子矩阵的和
给定一个二维矩阵,提供多次查询,计算子矩阵的和
解法
- 预处理(0,0)到(i,j)的子数组的和
#include <vector>
class NumMatrix {
public:
NumMatrix(const std::vector<std::vector<int>> &matrix) {
int m = matrix.size();
if (m == 0)
return;
int n = matrix[0].size();
sums.resize(m + 1, std::vector<int>(n + 1, 0));
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
sums[i + 1][j + 1] =
sums[i + 1][j] + sums[i][j + 1] - sums[i][j] + matrix[i][j];
}
}
}
int SumRegion(int row1, int col1, int row2, int col2) {
return sums[row2 + 1][col2 + 1] - sums[row1][col2 + 1] -
sums[row2 + 1][col1] + sums[row1][col1];
}
private:
std::vector<std::vector<int>> sums;
};
package main
type NumMatrix struct {
sums [][]int
}
func Constructor(matrix [][]int) NumMatrix {
m := len(matrix)
if m == 0 {
return NumMatrix{}
}
n := len(matrix[0])
sums := make([][]int, m+1)
sums[0] = make([]int, n+1)
for i, row := range matrix {
sums[i+1] = make([]int, n+1)
for j, v := range row {
sums[i+1][j+1] = sums[i+1][j] + sums[i][j+1] - sums[i][j] + v
}
}
return NumMatrix{sums}
}
func (nm *NumMatrix) SumRegion(row1, col1, row2, col2 int) int {
return nm.sums[row2+1][col2+1] - nm.sums[row1][col2+1] - nm.sums[row2+1][col1] + nm.sums[row1][col1]
}
14、
剑指 Offer II 014. 字符串中的变位词
给定两个字符串,判断字符串的排列之一是否是另一个字符串的子串
解法
- 使用26长度的数组来保存字符情况
#include <string>
#include <vector>
bool checkInclusion(const std::string &s1, const std::string &s2) {
int n = s1.size(), m = s2.size();
if (n > m)
return false;
std::vector<int> cnt(26, 0);
for (char ch : s1) {
cnt[ch - 'a']--;
}
int left = 0;
for (int right = 0; right < m; ++right) {
int x = s2[right] - 'a';
cnt[x]++;
while (cnt[x] > 0) {
cnt[s2[left] - 'a']--;
left++;
}
if (right - left + 1 == n) {
return true;
}
}
return false;
}
package main
func checkInclusion(s1, s2 string) bool {
n, m := len(s1), len(s2)
if n > m {
return false
}
cnt := [26]int{}
for _, ch := range s1 {
cnt[ch-'a']--
}
left := 0
for right, ch := range s2 {
x := ch - 'a'
cnt[x]++
for cnt[x] > 0 {
cnt[s2[left]-'a']--
left++
}
if right-left+1 == n {
return true
}
}
return false
}
15、
剑指 Offer II 015. 字符串中的所有变位词
给定两个字符串,返回一个子串的所有变位形式判断在另一个字符串中是子串的话,就返回其起始下标
解法
- 26位长度数组
- 滑动窗口
#include <string>
#include <vector>
std::vector<int> findAnagrams(const std::string &s, const std::string &p) {
std::vector<int> ans;
int sLen = s.size(), pLen = p.size();
if (sLen < pLen)
return ans;
std::vector<int> sCount(26, 0), pCount(26, 0);
for (int i = 0; i < pLen; ++i) {
sCount[s[i] - 'a']++;
pCount[p[i] - 'a']++;
}
if (sCount == pCount) {
ans.push_back(0);
}
for (int i = 0; i < sLen - pLen; ++i) {
sCount[s[i] - 'a']--;
sCount[s[i + pLen] - 'a']++;
if (sCount == pCount) {
ans.push_back(i + 1);
}
}
return ans;
}
package main
func findAnagrams(s, p string) (ans []int) {
sLen, pLen := len(s), len(p)
if sLen < pLen {
return
}
var sCount, pCount [26]int
for i, ch := range p {
sCount[s[i]-'a']++
pCount[ch-'a']++
}
if sCount == pCount {
ans = append(ans, 0)
}
for i, ch := range s[:sLen-pLen] {
sCount[ch-'a']--
sCount[s[i+pLen]-'a']++
if sCount == pCount {
ans = append(ans, i+1)
}
}
return
}
16、
剑指 Offer II 016. 不含重复字符的最长子字符串
给定一个字符串,返回不包含重复字符的最长子字符串长度
解法
- 滑动窗口,记录在窗口中字符的个数
#include <algorithm>
#include <string>
#include <unordered_map>
int lengthOfLongestSubstring(const std::string &s) {
std::unordered_map<char, int> m;
int n = s.length();
int rk = -1, ans = 0;
for (int i = 0; i < n; ++i) {
if (i != 0) {
m.erase(s[i - 1]);
}
while (rk + 1 < n && m[s[rk + 1]] == 0) {
m[s[++rk]]++;
}
ans = std::max(ans, rk - i + 1);
}
return ans;
}
package main
func lengthOfLongestSubstring(s string) int {
// 哈希集合,记录每个字符是否出现过
m := map[byte]int{}
n := len(s)
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
rk, ans := -1, 0
for i := 0; i < n; i++ {
if i != 0 {
// 左指针向右移动一格,移除一个字符
delete(m, s[i-1])
}
for rk+1 < n && m[s[rk+1]] == 0 {
// 不断地移动右指针
m[s[rk+1]]++
rk++
}
// 第 i 到 rk 个字符是一个极长的无重复字符子串
ans = max(ans, rk-i+1)
}
return ans
}
17、
剑指 Offer II 017. 含有所有字符的最短字符串
给定两个字符串s和t,找到s中的一个最短字符串,包含t的所有字符,如果不存在这样的子字符串,则返回空字符串
#include <climits>
#include <string>
#include <unordered_map>
std::string minWindow(const std::string &s, const std::string &t) {
std::unordered_map<char, int> ori, cnt;
for (char c : t) {
ori[c]++;
}
int sLen = s.length();
int minLen = INT_MAX;
int ansL = -1, ansR = -1;
auto check = [&]() -> bool {
for (const auto &[k, v] : ori) {
if (cnt[k] < v) {
return false;
}
}
return true;
};
for (int l = 0, r = 0; r < sLen; ++r) {
if (ori.find(s[r]) != ori.end()) {
cnt[s[r]]++;
}
while (check() && l <= r) {
if (r - l + 1 < minLen) {
minLen = r - l + 1;
ansL = l;
ansR = l + minLen;
}
if (ori.find(s[l]) != ori.end()) {
cnt[s[l]]--;
}
++l;
}
}
if (ansL == -1) {
return "";
}
return s.substr(ansL, ansR - ansL);
}
package main
import "math"
func minWindow(s string, t string) string {
ori, cnt := map[byte]int{}, map[byte]int{}
for i := 0; i < len(t); i++ {
ori[t[i]]++
}
sLen := len(s)
len := math.MaxInt32
ansL, ansR := -1, -1
check := func() bool {
for k, v := range ori {
if cnt[k] < v {
return false
}
}
return true
}
for l, r := 0, 0; r < sLen; r++ {
if r < sLen && ori[s[r]] > 0 {
cnt[s[r]]++
}
for check() && l <= r {
if r-l+1 < len {
len = r - l + 1
ansL, ansR = l, l+len
}
if _, ok := ori[s[l]]; ok {
cnt[s[l]] -= 1
}
l++
}
}
if ansL == -1 {
return ""
}
return s[ansL:ansR]
}
18、
剑指 Offer II 018. 有效的回文
给定一个字符串,验证是否是回文字符串, 只考虑字母和数字字符,可以忽略字母的大小写。
#include <algorithm>
#include <cctype>
#include <string>
bool isalnum(char ch) {
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
(ch >= '0' && ch <= '9');
}
bool isPalindrome(const std::string &s) {
std::string sgood;
for (char ch : s) {
if (isalnum(ch)) {
sgood += std::tolower(ch);
}
}
int n = sgood.length();
for (int i = 0; i < n / 2; ++i) {
if (sgood[i] != sgood[n - 1 - i]) {
return false;
}
}
return true;
}
package main
import "strings"
func isPalindrome(s string) bool {
var sgood string
for i := 0; i < len(s); i++ {
if isalnum(s[i]) {
sgood += string(s[i])
}
}
n := len(sgood)
sgood = strings.ToLower(sgood)
for i := 0; i < n/2; i++ {
if sgood[i] != sgood[n-1-i] {
return false
}
}
return true
}
func isalnum(ch byte) bool {
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')
}
19、
剑指 Offer II 019. 最多删除一个字符得到回文
给定一个字符串,删除其中一个字符判断是否是回文
#include <string>
bool validPalindrome(const std::string &s) {
int low = 0, high = s.length() - 1;
while (low < high) {
if (s[low] == s[high]) {
++low;
--high;
} else {
bool flag1 = true, flag2 = true;
for (int i = low, j = high - 1; i < j; ++i, --j) {
if (s[i] != s[j]) {
flag1 = false;
break;
}
}
for (int i = low + 1, j = high; i < j; ++i, --j) {
if (s[i] != s[j]) {
flag2 = false;
break;
}
}
return flag1 || flag2;
}
}
return true;
}
package main
func validPalindrome(s string) bool {
low, high := 0, len(s)-1
for low < high {
if s[low] == s[high] {
low++
high--
} else {
flag1, flag2 := true, true
for i, j := low, high-1; i < j; i, j = i+1, j-1 {
if s[i] != s[j] {
flag1 = false
break
}
}
for i, j := low+1, high; i < j; i, j = i+1, j-1 {
if s[i] != s[j] {
flag2 = false
break
}
}
return flag1 || flag2
}
}
return true
}
20、
剑指 Offer II 020. 回文子字符串的个数
计算一个字符串中有多少个回文子字符串
#include <string>
int countSubstrings(const std::string &s) {
int n = s.length();
int ans = 0;
for (int i = 0; i < 2 * n - 1; ++i) {
int l = i / 2;
int r = i / 2 + i % 2;
while (l >= 0 && r < n && s[l] == s[r]) {
--l;
++r;
++ans;
}
}
return ans;
}
package main
func countSubstrings(s string) int {
n := len(s)
ans := 0
for i := 0; i < 2*n-1; i++ {
l, r := i/2, i/2+i%2
for l >= 0 && r < n && s[l] == s[r] {
l--
r++
ans++
}
}
return ans
}
21、
剑指 Offer II 021. 删除链表的倒数第 n 个结点
给定一个链表,删除链表的倒数第n个节点,并且返回头节点
解法
- 快慢指针
struct ListNode {
int Val;
ListNode *Next;
ListNode(int x) : Val(x), Next(nullptr) {}
};
ListNode *removeNthFromEnd(ListNode *head, int n) {
ListNode *dummy = new ListNode(0);
dummy->Next = head;
ListNode *first = head;
ListNode *second = dummy;
for (int i = 0; i < n; ++i) {
first = first->Next;
}
while (first != nullptr) {
first = first->Next;
second = second->Next;
}
second->Next = second->Next->Next;
ListNode *newHead = dummy->Next;
delete dummy; // Free the allocated memory for dummy node
return newHead;
}
package main
type ListNode struct {
Val int
Next *ListNode
}
func removeNthFromEnd(head *ListNode, n int) *ListNode {
dummy := &ListNode{0, head}
first, second := head, dummy
for i := 0; i < n; i++ {
first = first.Next
}
for ; first != nil; first = first.Next {
second = second.Next
}
second.Next = second.Next.Next
return dummy.Next
}
22、
剑指 Offer II 022. 链表中环的入口节点
给定一个数组形式的链表,数组中每个元素对应着下一个节点的下标,如果该链表没有环,就返回-1,如果有环,则返回环入口的下标
解法
- 快慢指针,当相遇了之后从当前以及初始位置以相同速度前进,当再次相遇就是环的入口
struct ListNode {
int Val;
ListNode *Next;
ListNode(int x) : Val(x), Next(nullptr) {}
};
ListNode *detectCycle(ListNode *head) {
if (!head)
return nullptr;
ListNode *slow = head;
ListNode *fast = head;
while (fast != nullptr && fast->Next != nullptr) {
slow = slow->Next;
fast = fast->Next->Next;
if (slow == fast) {
ListNode *p = head;
while (p != slow) {
p = p->Next;
slow = slow->Next;
}
return p;
}
}
return nullptr;
}
package main
func detectCycle(head *ListNode) *ListNode {
slow, fast := head, head
for fast != nil {
slow = slow.Next
if fast.Next == nil {
return nil
}
fast = fast.Next.Next
if fast == slow {
p := head
for p != slow {
p = p.Next
slow = slow.Next
}
return p
}
}
return nil
}
23、
剑指 Offer II 023. 两个链表的第一个重合节点
给定两个链表,题目保证该链式结构没有环,找到该两个链表的相交节点,如果不存在相交节点,就返回空
解法
- 链表走完了之后到另外一个上,这样到达相交节点的节点数相同,是会相遇的
struct ListNode {
int Val;
ListNode *Next;
ListNode(int x) : Val(x), Next(nullptr) {}
};
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == nullptr || headB == nullptr) {
return nullptr;
}
ListNode *pa = headA;
ListNode *pb = headB;
while (pa != pb) {
pa = (pa == nullptr) ? headB : pa->Next;
pb = (pb == nullptr) ? headA : pb->Next;
}
return pa;
}
package main
func getIntersectionNode(headA, headB *ListNode) *ListNode {
if headA == nil || headB == nil {
return nil
}
pa, pb := headA, headB
for pa != pb {
if pa == nil {
pa = headB
} else {
pa = pa.Next
}
if pb == nil {
pb = headA
} else {
pb = pb.Next
}
}
return pa
}
24、
剑指 Offer II 024. 反转链表
给定一个链式结构,反转后返回其头节点
struct ListNode {
int Val;
ListNode *Next;
ListNode(int x) : Val(x), Next(nullptr) {}
};
ListNode *reverseList(ListNode *head) {
if (head == nullptr || head->Next == nullptr) {
return head;
}
ListNode *newHead = reverseList(head->Next);
head->Next->Next = head;
head->Next = nullptr;
return newHead;
}
package main
func reverseList(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
newHead := reverseList(head.Next)
head.Next.Next = head
head.Next = nil
return newHead
}
25、
剑指 Offer II 025. 链表中的两数相加
使用两个链表表示非负整数,尾节点为个位数,返回两个链表的相加结果,并以链表形式返回
解法
- 将链表转为数组,然后计算完后进行重新构造成链表
#include <vector>
struct ListNode {
int Val;
ListNode *Next;
ListNode(int x) : Val(x), Next(nullptr) {}
};
ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) {
std::vector<int> s1, s2;
while (l1 != nullptr) {
s1.push_back(l1->Val);
l1 = l1->Next;
}
while (l2 != nullptr) {
s2.push_back(l2->Val);
l2 = l2->Next;
}
int carry = 0;
ListNode *head = nullptr;
while (!s1.empty() || !s2.empty() || carry != 0) {
int sum = 0;
if (!s1.empty()) {
sum += s1.back();
s1.pop_back();
}
if (!s2.empty()) {
sum += s2.back();
s2.pop_back();
}
sum += carry;
ListNode *node = new ListNode(sum % 10);
node->Next = head;
head = node;
carry = sum / 10;
}
return head;
}
package main
func addTwoNumbers(l1 *ListNode, l2 *ListNode) (head *ListNode) {
var s1, s2 []int
for l1 != nil {
s1 = append(s1, l1.Val)
l1 = l1.Next
}
for l2 != nil {
s2 = append(s2, l2.Val)
l2 = l2.Next
}
carry := 0
for len(s1) > 0 || len(s2) > 0 || carry > 0 {
sum := 0
if len(s1) > 0 {
sum += s1[len(s1)-1]
s1 = s1[:len(s1)-1]
}
if len(s2) > 0 {
sum += s2[len(s2)-1]
s2 = s2[:len(s2)-1]
}
sum += carry
node := &ListNode{Val: sum % 10}
node.Next = head
head = node
carry = sum / 10
}
return
}
26、
剑指 Offer II 026. 重排链表
给定一个链表结构,原始为'1 2 3 4 5 6 ... n-1 n' 修改为'1 n 2 n-1 3 n-2 ...'
#include <vector>
struct ListNode {
int Val;
ListNode *Next;
ListNode(int x) : Val(x), Next(nullptr) {}
};
void reorderList(ListNode *head) {
if (head == nullptr) {
return;
}
std::vector<ListNode *> nodes;
for (ListNode *node = head; node != nullptr; node = node->Next) {
nodes.push_back(node);
}
int i = 0, j = nodes.size() - 1;
while (i < j) {
nodes[i]->Next = nodes[j];
++i;
if (i == j) {
break;
}
nodes[j]->Next = nodes[i];
--j;
}
nodes[i]->Next = nullptr;
}
package main
func reorderList(head *ListNode) {
if head == nil {
return
}
nodes := []*ListNode{}
for node := head; node != nil; node = node.Next {
nodes = append(nodes, node)
}
i, j := 0, len(nodes)-1
for i < j {
nodes[i].Next = nodes[j]
i++
if i == j {
break
}
nodes[j].Next = nodes[i]
j--
}
nodes[i].Next = nil
}
27、
剑指 Offer II 027. 回文链表
给定一个链表结构,判断该链表是否是回文
#include <vector>
struct ListNode {
int Val;
ListNode *Next;
ListNode(int x) : Val(x), Next(nullptr) {}
};
bool isLinkListPalindrome(ListNode *head) {
std::vector<int> vals;
for (ListNode *node = head; node != nullptr; node = node->Next) {
vals.push_back(node->Val);
}
int n = vals.size();
for (int i = 0; i < n / 2; ++i) {
if (vals[i] != vals[n - 1 - i]) {
return false;
}
}
return true;
}
package main
func isLinkListPalindrome(head *ListNode) bool {
vals := []int{}
for ; head != nil; head = head.Next {
vals = append(vals, head.Val)
}
n := len(vals)
for i, v := range vals[:n/2] {
if v != vals[n-1-i] {
return false
}
}
return true
}
28、
剑指 Offer II 028. 展平多级双向链表
将多级双向链表展平
这里多级双向链表就是包含前后指针,还包含子链表的指针,(其实就相当于链表)
struct Node {
int Val;
Node *Prev;
Node *Next;
Node *Child;
Node(int x) : Val(x), Prev(nullptr), Next(nullptr), Child(nullptr) {}
};
Node *dfs(Node *node) {
Node *cur = node;
Node *last = nullptr;
while (cur != nullptr) {
Node *next = cur->Next;
if (cur->Child != nullptr) {
Node *childLast = dfs(cur->Child);
cur->Next = cur->Child;
cur->Child->Prev = cur;
if (next != nullptr) {
childLast->Next = next;
next->Prev = childLast;
}
cur->Child = nullptr;
last = childLast;
} else {
last = cur;
}
cur = next;
}
return last;
}
Node *flatten(Node *root) {
dfs(root);
return root;
}
package main
type LinkNode struct {
Val int
Next *LinkNode
Prev *LinkNode
Child *LinkNode
}
func dfs(node *LinkNode) (last *LinkNode) {
cur := node
for cur != nil {
next := cur.Next
// 如果有子节点,那么首先处理子节点
if cur.Child != nil {
childLast := dfs(cur.Child)
next = cur.Next
// 将 node 与 child 相连
cur.Next = cur.Child
cur.Child.Prev = cur
// 如果 next 不为空,就将 last 与 next 相连
if next != nil {
childLast.Next = next
next.Prev = childLast
}
// 将 child 置为空
cur.Child = nil
last = childLast
} else {
last = cur
}
cur = next
}
return
}
func flatten(root *LinkNode) *LinkNode {
dfs(root)
return root
}
29、
剑指 Offer II 029. 排序的循环链表
给定一个循环链表,它的值是单调非递减的,提供一个元素插入的方法,使得元素插入之后值依然是单调非递减的
解法
- 第一种情况
cur.val <= val <= next.val
, 直接插入到这里 - 第二种情况
cur.val > next.val
, 说明是链表遍历完了,break,直接加入到cur的后面
struct Node {
int Val;
Node *Next;
Node(int x) : Val(x), Next(nullptr) {}
};
Node *insert(Node *head, int insertVal) {
Node *node = new Node(insertVal);
if (head == nullptr) {
node->Next = node;
return node;
}
if (head->Next == head) {
head->Next = node;
node->Next = head;
return head;
}
Node *curr = head;
Node *next = head->Next;
while (next != head) {
if (insertVal >= curr->Val && insertVal <= next->Val) {
break;
}
if (curr->Val > next->Val) {
if (insertVal > curr->Val || insertVal < next->Val) {
break;
}
}
curr = curr->Next;
next = next->Next;
}
curr->Next = node;
node->Next = next;
return head;
}
package main
func insert(head *ListNode, insertVal int) *ListNode {
node := &ListNode{Val: insertVal}
if head == nil {
node.Next = node
return node
}
if head.Next == head {
head.Next = node
node.Next = head
return head
}
curr, next := head, head.Next
for next != head {
if insertVal >= curr.Val && insertVal <= next.Val {
break
}
if curr.Val > next.Val {
if insertVal > curr.Val || insertVal < next.Val {
break
}
}
curr = curr.Next
next = next.Next
}
curr.Next = node
node.Next = next
return head
}
30、
剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器
创造一个数据结构,它的插入、删除、随机访问都是O(1)时间复杂度
解法
- 由于需要随机访问,所以将数据存储到数组中,通过对下标进行随机来进行访问
- 插入和删除也需要O(1), 使用map来存储值与下标
- 当删除的时候,将末尾的一个移动到要删除的位置并且更新下对应下标,然后数组只需要减一个长度即可
#include <cstdlib>
#include <unordered_map>
#include <vector>
class RandomizedSet {
public:
RandomizedSet() {}
bool insert(int val) {
if (indices.find(val) != indices.end()) {
return false;
}
indices[val] = nums.size();
nums.push_back(val);
return true;
}
bool remove(int val) {
auto it = indices.find(val);
if (it == indices.end()) {
return false;
}
int last = nums.back();
nums[it->second] = last;
indices[last] = it->second;
nums.pop_back();
indices.erase(val);
return true;
}
int getRandom() {
int randomIndex = rand() % nums.size();
return nums[randomIndex];
}
private:
std::vector<int> nums;
std::unordered_map<int, int> indices;
};
package main
import "math/rand"
type RandomizedSet struct {
nums []int
indices map[int]int
}
func NewRandomizedSet() RandomizedSet {
return RandomizedSet{
[]int{}, map[int]int{},
}
}
func (rs *RandomizedSet) Insert(val int) bool {
if _, ok := rs.indices[val]; ok {
return false
}
rs.indices[val] = len(rs.nums)
rs.nums = append(rs.nums, val)
return true
}
func (rs *RandomizedSet) Remove(val int) bool {
id, ok := rs.indices[val]
if !ok {
return false
}
last := len(rs.nums) - 1
rs.nums[id] = rs.nums[last]
rs.indices[rs.nums[id]] = id
rs.nums = rs.nums[:last]
delete(rs.indices, val)
return true
}
func (rs *RandomizedSet) GetRandom() int {
return rs.nums[rand.Intn(len(rs.nums))]
}
31、
剑指 Offer II 031. 最近最少使用缓存
设计一个最近最少使用的缓存数据结构,提供get和put方法
#include <list>
#include <unordered_map>
class LRUCache {
public:
LRUCache(int capacity) : capacity(capacity) {}
int get(int key) {
auto it = cache.find(key);
if (it == cache.end()) {
return -1;
}
// Move the accessed element to the front of the list
list.splice(list.begin(), list, it->second);
return it->second->second;
}
void put(int key, int value) {
auto it = cache.find(key);
if (it != cache.end()) {
// Update the value and move the element to the front of the list
it->second->second = value;
list.splice(list.begin(), list, it->second);
return;
}
if (list.size() == capacity) {
// Remove the least recently used element
int lruKey = list.back().first;
list.pop_back();
cache.erase(lruKey);
}
// Insert the new element at the front of the list
list.emplace_front(key, value);
cache[key] = list.begin();
}
private:
int capacity;
std::list<std::pair<int, int>> list;
std::unordered_map<int, std::list<std::pair<int, int>>::iterator> cache;
};
package main
import "container/list"
type LRUCache struct {
capacity int
cache map[int]*list.Element
list *list.List
}
type entry struct {
key int
value int
}
func NewLRUCache(capacity int) LRUCache {
return LRUCache{
capacity: capacity,
cache: make(map[int]*list.Element),
list: list.New(),
}
}
func (this *LRUCache) Get(key int) int {
if elem, ok := this.cache[key]; ok {
this.list.MoveToFront(elem)
return elem.Value.(*entry).value
}
return -1
}
func (this *LRUCache) Put(key int, value int) {
if elem, ok := this.cache[key]; ok {
elem.Value.(*entry).value = value
this.list.MoveToFront(elem)
return
}
if this.list.Len() == this.capacity {
lastElem := this.list.Back()
delete(this.cache, lastElem.Value.(*entry).key)
this.list.Remove(lastElem)
}
newElem := this.list.PushFront(&entry{key: key, value: value})
this.cache[key] = newElem
}
32、
剑指 Offer II 032. 有效的变位词
判断字符串s和t是否是一组有效的变位词,即元素相同位置不同
#include <string>
#include <vector>
bool isAnagram(const std::string &s, const std::string &t) {
if (s == t) {
return false;
}
std::vector<int> c1(26, 0), c2(26, 0);
for (char ch : s) {
c1[ch - 'a']++;
}
for (char ch : t) {
c2[ch - 'a']++;
}
return c1 == c2;
}
package main
func isAnagram(s, t string) bool {
if s == t {
return false
}
var c1, c2 [26]int
for _, ch := range s {
c1[ch-'a']++
}
for _, ch := range t {
c2[ch-'a']++
}
return c1 == c2
}
33、
剑指 Offer II 033. 变位词组
给定一个字符串数组,将所有是同一类变位词的组合在一起
#include <array>
#include <string>
#include <unordered_map>
#include <vector>
std::vector<std::vector<std::string>>
groupAnagrams(std::vector<std::string> &strs) {
std::unordered_map<std::array<int, 26>, std::vector<std::string>> mp;
for (const auto &str : strs) {
std::array<int, 26> cnt = {0};
for (char ch : str) {
cnt[ch - 'a']++;
}
mp[cnt].push_back(str);
}
std::vector<std::vector<std::string>> ans;
for (auto &[key, group] : mp) {
ans.push_back(std::move(group));
}
return ans;
}
package main
func groupAnagrams(strs []string) [][]string {
mp := map[[26]int][]string{}
for _, str := range strs {
cnt := [26]int{}
for _, b := range str {
cnt[b-'a']++
}
mp[cnt] = append(mp[cnt], str)
}
ans := make([][]string, 0, len(mp))
for _, v := range mp {
ans = append(ans, v)
}
return ans
}
34、
剑指 Offer II 034. 外星语言是否排序
给定一种新的order字符顺序,判断一个字符串数组words中的字符串是否是按照字典序进行排列的
#include <string>
#include <unordered_map>
#include <vector>
bool isAlienSorted(const std::vector<std::string> &words,
const std::string &order) {
std::vector<int> index(26, 0);
for (int i = 0; i < order.size(); ++i) {
index[order[i] - 'a'] = i;
}
for (int i = 1; i < words.size(); ++i) {
const std::string &prev = words[i - 1];
const std::string &curr = words[i];
for (int j = 0; j < std::min(prev.size(), curr.size()); ++j) {
int pre = index[prev[j] - 'a'];
int cur = index[curr[j] - 'a'];
if (pre > cur) {
return false;
}
if (pre < cur) {
goto next_word;
}
}
if (prev.size() > curr.size()) {
return false;
}
next_word:;
}
return true;
}
package main
func isAlienSorted(words []string, order string) bool {
index := [26]int{}
for i, c := range order {
index[c-'a'] = i
}
next:
for i := 1; i < len(words); i++ {
for j := 0; j < len(words[i-1]) && j < len(words[i]); j++ {
pre, cur := index[words[i-1][j]-'a'], index[words[i][j]-'a']
if pre > cur {
return false
}
if pre < cur {
continue next
}
}
if len(words[i-1]) > len(words[i]) {
return false
}
}
return true
}
35、
剑指 Offer II 035. 最小时间差
给定一个字符串列表,其中字符串是以"HH:MM"的24小时时间格式进行表示,找到列表中时间差最小值,并以分钟格式进行表示
#include <algorithm>
#include <climits>
#include <string>
#include <vector>
int getMinutes(const std::string &t) {
return (t[0] - '0') * 10 * 60 + (t[1] - '0') * 60 + (t[3] - '0') * 10 +
(t[4] - '0');
}
int findMinDifference(std::vector<std::string> &timePoints) {
if (timePoints.size() > 1440) {
return 0;
}
std::sort(timePoints.begin(), timePoints.end());
int ans = INT_MAX;
int t0Minutes = getMinutes(timePoints[0]);
int preMinutes = t0Minutes;
for (int i = 1; i < timePoints.size(); ++i) {
int minutes = getMinutes(timePoints[i]);
ans = std::min(ans,
minutes - preMinutes); // Difference between adjacent times
preMinutes = minutes;
}
ans = std::min(ans,
t0Minutes + 1440 -
preMinutes); // Difference between the first and last time
return ans;
}
int min(int a, int b) { return a > b ? b : a; }
package main
import (
"math"
"sort"
)
func getMinutes(t string) int {
return (int(t[0]-'0')*10+int(t[1]-'0'))*60 + int(t[3]-'0')*10 + int(t[4]-'0')
}
func findMinDifference(timePoints []string) int {
if len(timePoints) > 1440 {
return 0
}
sort.Strings(timePoints)
ans := math.MaxInt32
t0Minutes := getMinutes(timePoints[0])
preMinutes := t0Minutes
for _, t := range timePoints[1:] {
minutes := getMinutes(t)
ans = min(ans, minutes-preMinutes) // 相邻时间的时间差
preMinutes = minutes
}
ans = min(ans, t0Minutes+1440-preMinutes) // 首尾时间的时间差
return ans
}
func min(a, b int) int {
if a > b {
return b
}
return a
}
36、
剑指 Offer II 036. 后缀表达式
给定一个token字符串列表,该token列表是逆波兰表达式,用来求该后缀表达式的结果,整数除法只保留整数部分
#include <stack>
#include <stdexcept>
#include <string>
#include <vector>
int evalRPN(const std::vector<std::string> &tokens) {
std::stack<int> stack;
for (const auto &token : tokens) {
if (isdigit(token.back())) {
stack.push(std::stoi(token));
} else {
int b = stack.top();
stack.pop();
int a = stack.top();
stack.pop();
if (token == "+") {
stack.push(a + b);
} else if (token == "-") {
stack.push(a - b);
} else if (token == "*") {
stack.push(a * b);
} else if (token == "/") {
stack.push(a / b);
} else {
throw std::invalid_argument("Invalid operator");
}
}
}
return stack.top();
}
package main
import "strconv"
func evalRPN(tokens []string) int {
stack := make([]int, (len(tokens)+1)/2)
index := -1
for _, token := range tokens {
val, err := strconv.Atoi(token)
if err == nil {
index++
stack[index] = val
} else {
index--
switch token {
case "+":
stack[index] += stack[index+1]
case "-":
stack[index] -= stack[index+1]
case "*":
stack[index] *= stack[index+1]
default:
stack[index] /= stack[index+1]
}
}
}
return stack[0]
}
37、
剑指 Offer II 037. 小行星碰撞
给定一个整数数组用来表示同一行的小行星,绝对值表示大小,正负表示方向,正表示向右移动,负表示向左移动。
当两个行星相遇会进行碰撞,留下大小大的那个,大小相同两个一起爆炸
求解最后的小行星情况
#include <vector>
std::vector<int> asteroidCollision(const std::vector<int> &asteroids) {
std::vector<int> st;
for (int aster : asteroids) {
bool alive = true;
while (alive && aster < 0 && !st.empty() && st.back() > 0) {
alive = st.back() < -aster; // Check if the current asteroid survives
if (st.back() <= -aster) { // Top of the stack asteroid explodes
st.pop_back();
}
}
if (alive) {
st.push_back(aster);
}
}
return st;
}
package main
func asteroidCollision(asteroids []int) (st []int) {
for _, aster := range asteroids {
alive := true
for alive && aster < 0 && len(st) > 0 && st[len(st)-1] > 0 {
alive = st[len(st)-1] < -aster // aster 是否存在
if st[len(st)-1] <= -aster { // 栈顶小行星爆炸
st = st[:len(st)-1]
}
}
if alive {
st = append(st, aster)
}
}
return
}
38、
剑指 Offer II 038. 每日温度
给定一个每天的气温列表,返回最近的一个温度更高的相差的天数
#include <stack>
#include <vector>
std::vector<int> dailyTemperatures(const std::vector<int> &temperatures) {
int length = temperatures.size();
std::vector<int> ans(length, 0);
std::stack<int> stack;
for (int i = 0; i < length; ++i) {
int temperature = temperatures[i];
while (!stack.empty() && temperature > temperatures[stack.top()]) {
int prevIndex = stack.top();
stack.pop();
ans[prevIndex] = i - prevIndex;
}
stack.push(i);
}
return ans;
}
package main
func dailyTemperatures(temperatures []int) []int {
length := len(temperatures)
ans := make([]int, length)
stack := []int{}
for i := 0; i < length; i++ {
temperature := temperatures[i]
for len(stack) > 0 && temperature > temperatures[stack[len(stack)-1]] {
prevIndex := stack[len(stack)-1]
stack = stack[:len(stack)-1]
ans[prevIndex] = i - prevIndex
}
stack = append(stack, i)
}
return ans
}
39、
剑指 Offer II 039. 直方图最大矩形面积
给定一个整数数组,表示一个二维平面,其中每个元素表示其y轴的高度,返回该二维平面中覆盖的最大的矩形面积
#include <algorithm>
#include <stack>
#include <vector>
int largestRectangleArea(const std::vector<int> &heights) {
int n = heights.size();
std::vector<int> left(n), right(n);
std::stack<int> mono_stack;
for (int i = 0; i < n; ++i) {
while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
mono_stack.pop();
}
left[i] = mono_stack.empty() ? -1 : mono_stack.top();
mono_stack.push(i);
}
while (!mono_stack.empty())
mono_stack.pop();
for (int i = n - 1; i >= 0; --i) {
while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
mono_stack.pop();
}
right[i] = mono_stack.empty() ? n : mono_stack.top();
mono_stack.push(i);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans = std::max(ans, (right[i] - left[i] - 1) * heights[i]);
}
return ans;
}
int max(int x, int y) { return x > y ? x : y; }
package main
func largestRectangleArea(heights []int) int {
n := len(heights)
left, right := make([]int, n), make([]int, n)
mono_stack := []int{}
for i := 0; i < n; i++ {
for len(mono_stack) > 0 && heights[mono_stack[len(mono_stack)-1]] >= heights[i] {
mono_stack = mono_stack[:len(mono_stack)-1]
}
if len(mono_stack) == 0 {
left[i] = -1
} else {
left[i] = mono_stack[len(mono_stack)-1]
}
mono_stack = append(mono_stack, i)
}
mono_stack = []int{}
for i := n - 1; i >= 0; i-- {
for len(mono_stack) > 0 && heights[mono_stack[len(mono_stack)-1]] >= heights[i] {
mono_stack = mono_stack[:len(mono_stack)-1]
}
if len(mono_stack) == 0 {
right[i] = n
} else {
right[i] = mono_stack[len(mono_stack)-1]
}
mono_stack = append(mono_stack, i)
}
ans := 0
for i := 0; i < n; i++ {
ans = max(ans, (right[i]-left[i]-1)*heights[i])
}
return ans
}
func max(x, y int) int {
if x > y {
return x
}
return y
}
40、
剑指 Offer II 040. 矩阵中最大的矩形
一个只包含0、1元素的矩阵,找到全部为1的元素的最大矩阵的面积
#include <algorithm>
#include <string>
#include <vector>
using namespace std;
int maximalRectangle(vector<string> &matrix) {
if (matrix.empty()) {
return 0;
}
int m = matrix.size(), n = matrix[0].size();
vector<vector<int>> left(m, vector<int>(n, 0));
int ans = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (matrix[i][j] == '0') {
continue;
}
if (j == 0) {
left[i][j] = 1;
} else {
left[i][j] = left[i][j - 1] + 1;
}
}
}
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (matrix[i][j] == '0') {
continue;
}
int width = left[i][j];
int area = width;
for (int k = i - 1; k >= 0; --k) {
width = min(width, left[k][j]);
if (width == 0) {
break;
}
area = max(area, (i - k + 1) * width);
}
ans = max(ans, area);
}
}
return ans;
}
package main
func maximalRectangle(matrix []string) (ans int) {
if len(matrix) == 0 {
return
}
m, n := len(matrix), len(matrix[0])
left := make([][]int, m)
for i, row := range matrix {
left[i] = make([]int, n)
for j, v := range row {
if v == '0' {
continue
}
if j == 0 {
left[i][j] = 1
} else {
left[i][j] = left[i][j-1] + 1
}
}
}
for i, row := range matrix {
for j, v := range row {
if v == '0' {
continue
}
width := left[i][j]
area := width
for k := i - 1; k >= 0; k-- {
width = min(width, left[k][j])
if width == 0 {
break
}
area = max(area, (i-k+1)*width)
}
ans = max(ans, area)
}
}
return
}
41、
剑指 Offer II 041. 滑动窗口的平均值
给定一个指定大小的窗口,提供数据流并返回这个窗口内的元素的平均值
#include <vector>
class MovingAverage {
public:
MovingAverage(int size) : size(size), sum(0) {}
double next(int val) {
if (q.size() == size) {
sum -= q.front();
q.erase(q.begin());
}
sum += val;
q.push_back(val);
return static_cast<double>(sum) / q.size();
}
private:
int size;
int sum;
std::vector<int> q;
};
package main
type MovingAverage struct {
size, sum int
q []int
}
func Constructor(size int) MovingAverage {
return MovingAverage{size: size}
}
func (m *MovingAverage) Next(val int) float64 {
if len(m.q) == m.size {
m.sum -= m.q[0]
m.q = m.q[1:]
}
m.sum += val
m.q = append(m.q, val)
return float64(m.sum) / float64(len(m.q))
}
42、
剑指 Offer II 042. 最近请求次数
记录请求的时间等,并且返回最近3000ms内的调用次数
#include <deque>
class RecentCounter {
public:
RecentCounter() {}
int ping(int t) {
q.push_back(t);
while (q.front() < t - 3000) {
q.pop_front();
}
return q.size();
}
private:
std::deque<int> q;
};
package main
type RecentCounter []int
func NewRecentCounter() (_ RecentCounter) { return }
func (q *RecentCounter) Ping(t int) int {
*q = append(*q, t)
for (*q)[0] < t-3000 {
*q = (*q)[1:]
}
return len(*q)
}
43、
剑指 Offer II 043. 往完全二叉树添加节点
一个尽量集中在左边的完全二叉树,提供插入以及获取根节点的元素
#include <queue>
#include <vector>
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class CBTInserter {
public:
CBTInserter(TreeNode *root) : root(root) {
std::queue<TreeNode *> q;
q.push(root);
while (!q.empty()) {
TreeNode *node = q.front();
q.pop();
if (node->left) {
q.push(node->left);
}
if (node->right) {
q.push(node->right);
}
if (!node->left || !node->right) {
candidate.push_back(node);
}
}
}
int insert(int val) {
TreeNode *child = new TreeNode(val);
TreeNode *node = candidate.front();
if (!node->left) {
node->left = child;
} else {
node->right = child;
candidate.erase(candidate.begin());
}
candidate.push_back(child);
return node->val;
}
TreeNode *get_root() { return root; }
private:
TreeNode *root;
std::vector<TreeNode *> candidate;
};
package main
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
type CBTInserter struct {
root *TreeNode
candidate []*TreeNode
}
func NewCBT(root *TreeNode) CBTInserter {
q := []*TreeNode{root}
candidate := []*TreeNode{}
for len(q) > 0 {
node := q[0]
q = q[1:]
if node.Left != nil {
q = append(q, node.Left)
}
if node.Right != nil {
q = append(q, node.Right)
}
if node.Left == nil || node.Right == nil {
candidate = append(candidate, node)
}
}
return CBTInserter{root, candidate}
}
func (c *CBTInserter) Insert(val int) int {
child := &TreeNode{Val: val}
node := c.candidate[0]
if node.Left == nil {
node.Left = child
} else {
node.Right = child
c.candidate = c.candidate[1:]
}
c.candidate = append(c.candidate, child)
return node.Val
}
func (c *CBTInserter) Get_root() *TreeNode {
return c.root
}
44、
剑指 Offer II 044. 二叉树每层的最大值
给定一个二叉树,找出二叉树每一层的最大值
#include <algorithm>
#include <vector>
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
std::vector<int> largestValues(TreeNode *root) {
std::vector<int> ans;
dfs(root, 0, ans);
return ans;
}
private:
void dfs(TreeNode *node, int curHeight, std::vector<int> &ans) {
if (!node) {
return;
}
if (curHeight == ans.size()) {
ans.push_back(node->val);
} else {
ans[curHeight] = std::max(ans[curHeight], node->val);
}
dfs(node->left, curHeight + 1, ans);
dfs(node->right, curHeight + 1, ans);
}
};
int max(int a, int b) { return b > a ? b : a; }
package main
func largestValues(root *TreeNode) (ans []int) {
var dfs func(*TreeNode, int)
dfs = func(node *TreeNode, curHeight int) {
if node == nil {
return
}
if curHeight == len(ans) {
ans = append(ans, node.Val)
} else {
ans[curHeight] = max(ans[curHeight], node.Val)
}
dfs(node.Left, curHeight+1)
dfs(node.Right, curHeight+1)
}
dfs(root, 0)
return
}
45、
剑指 Offer II 045. 二叉树最底层最左边的值
给定一个二叉树,找出二叉树最底层,最左边的元素
#include <algorithm>
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
int findBottomLeftValue(TreeNode *root) {
int curVal = 0;
int curHeight = 0;
dfs(root, 0, curHeight, curVal);
return curVal;
}
private:
void dfs(TreeNode *node, int height, int &curHeight, int &curVal) {
if (!node) {
return;
}
height++;
dfs(node->left, height, curHeight, curVal);
dfs(node->right, height, curHeight, curVal);
if (height > curHeight) {
curHeight = height;
curVal = node->val;
}
}
};
package main
func findBottomLeftValue(root *TreeNode) (curVal int) {
curHeight := 0
var dfs func(*TreeNode, int)
dfs = func(node *TreeNode, height int) {
if node == nil {
return
}
height++
dfs(node.Left, height)
dfs(node.Right, height)
if height > curHeight {
curHeight = height
curVal = node.Val
}
}
dfs(root, 0)
return
}
46、
剑指 Offer II 046. 二叉树的右侧视图
给定一个二叉树,返回其右边的第一层视图,并从上往下打印出来
#include <vector>
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
std::vector<int> rightSideView(TreeNode *root) {
std::vector<int> ans;
if (!root) {
return ans;
}
dfs(root, 0, ans);
return ans;
}
private:
void dfs(TreeNode *node, int depth, std::vector<int> &ans) {
if (!node) {
return;
}
if (depth == ans.size()) {
ans.push_back(node->val);
}
dfs(node->right, depth + 1, ans);
dfs(node->left, depth + 1, ans);
}
};
package main
func rightSideView(root *TreeNode) []int {
ans := []int{}
if root == nil {
return ans
}
var dfs func(node *TreeNode, depth int)
dfs = func(node *TreeNode, depth int) {
if node == nil {
return
}
if len(ans) == depth {
ans = append(ans, node.Val)
}
dfs(node.Right, depth+1)
dfs(node.Left, depth+1)
}
dfs(root, 0)
return ans
}
47、
剑指 Offer II 047. 二叉树剪枝
给定一个二叉树,对所有只包含0数据的子树进行剪枝
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
TreeNode *pruneTree(TreeNode *root) {
if (!root) {
return nullptr;
}
root->left = pruneTree(root->left);
root->right = pruneTree(root->right);
if (!root->left && !root->right && root->val == 0) {
return nullptr;
}
return root;
}
};
package main
func pruneTree(root *TreeNode) *TreeNode {
if root == nil {
return nil
}
root.Left = pruneTree(root.Left)
root.Right = pruneTree(root.Right)
if root.Left == nil && root.Right == nil && root.Val == 0 {
return nil
}
return root
}
48、
剑指 Offer II 048. 序列化与反序列化二叉树
将二叉树进行序列化和反序列化
#include <queue>
#include <sstream>
#include <string>
#include <vector>
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Codec {
public:
// Serializes a tree to a single string.
std::string serialize(TreeNode *root) {
std::ostringstream sb;
serializeHelper(root, sb);
return sb.str();
}
// Deserializes your encoded data to tree.
TreeNode *deserialize(const std::string &data) {
std::vector<std::string> nodes = split(data, ',');
int index = 0;
return deserializeHelper(nodes, index);
}
private:
void serializeHelper(TreeNode *node, std::ostringstream &sb) {
if (!node) {
sb << "null,";
return;
}
sb << node->val << ',';
serializeHelper(node->left, sb);
serializeHelper(node->right, sb);
}
TreeNode *deserializeHelper(const std::vector<std::string> &nodes,
int &index) {
if (index >= nodes.size() || nodes[index] == "null") {
++index;
return nullptr;
}
TreeNode *node = new TreeNode(std::stoi(nodes[index++]));
node->left = deserializeHelper(nodes, index);
node->right = deserializeHelper(nodes, index);
return node;
}
std::vector<std::string> split(const std::string &s, char delimiter) {
std::vector<std::string> tokens;
std::string token;
std::istringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter)) {
tokens.push_back(token);
}
return tokens;
}
};
package main
import (
"strconv"
"strings"
)
type Codec struct{}
func NewCodeC() (_ Codec) {
return
}
func (Codec) serialize(root *TreeNode) string {
sb := &strings.Builder{}
var dfs func(*TreeNode)
dfs = func(node *TreeNode) {
if node == nil {
sb.WriteString("null,")
return
}
sb.WriteString(strconv.Itoa(node.Val))
sb.WriteByte(',')
dfs(node.Left)
dfs(node.Right)
}
dfs(root)
return sb.String()
}
func (Codec) deserialize(data string) *TreeNode {
sp := strings.Split(data, ",")
var build func() *TreeNode
build = func() *TreeNode {
if sp[0] == "null" {
sp = sp[1:]
return nil
}
val, _ := strconv.Atoi(sp[0])
sp = sp[1:]
return &TreeNode{val, build(), build()}
}
return build()
}
49、
剑指 Offer II 049. 从根节点到叶节点的路径数字之和
给定一个二叉树,获取从根节点到叶子节点的所有路径所表示的数字之和。
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
int sumNumbers(TreeNode *root) { return dfs(root, 0); }
private:
int dfs(TreeNode *node, int prevSum) {
if (!node) {
return 0;
}
int sum = prevSum * 10 + node->val;
if (!node->left && !node->right) {
return sum;
}
return dfs(node->left, sum) + dfs(node->right, sum);
}
};
package main
func dfs(root *TreeNode, prevSum int) int {
if root == nil {
return 0
}
sum := prevSum*10 + root.Val
if root.Left == nil && root.Right == nil {
return sum
}
return dfs(root.Left, sum) + dfs(root.Right, sum)
}
func sumNumbers(root *TreeNode) int {
return dfs(root, 0)
}
50、
剑指 Offer II 050. 向下的路径节点之和
给定一个二叉树,返回其中路径和为target的路径个数,其中这路径只能从上往下
#include <unordered_map>
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
int pathSum(TreeNode *root, int targetSum) {
std::unordered_map<long long, int> preSum;
preSum[0] = 1;
return dfs(root, 0, targetSum, preSum);
}
private:
int dfs(TreeNode *node, long long curr, int targetSum,
std::unordered_map<long long, int> &preSum) {
if (!node) {
return 0;
}
curr += node->val;
int ans = preSum[curr - targetSum];
preSum[curr]++;
ans += dfs(node->left, curr, targetSum, preSum);
ans += dfs(node->right, curr, targetSum, preSum);
preSum[curr]--;
return ans;
}
};
package main
func pathSum(root *TreeNode, targetSum int) (ans int) {
preSum := map[int64]int{0: 1}
var dfs func(*TreeNode, int64)
dfs = func(node *TreeNode, curr int64) {
if node == nil {
return
}
curr += int64(node.Val)
ans += preSum[curr-int64(targetSum)]
preSum[curr]++
dfs(node.Left, curr)
dfs(node.Right, curr)
preSum[curr]--
return
}
dfs(root, 0)
return
}
51、
剑指 Offer II 051. 节点之和最大的路径
给定一个二叉树,返回其最大的路径和,这里的路径可以是任意两个节点之间相连的路径
#include <algorithm>
#include <climits>
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
int maxPathSum(TreeNode *root) {
maxSum = INT_MIN;
maxGain(root);
return maxSum;
}
private:
int maxSum;
int maxGain(TreeNode *node) {
if (!node) {
return 0;
}
// Recursively calculate the maximum contribution of the left and right
// subtrees Only consider positive contributions
int leftGain = std::max(maxGain(node->left), 0);
int rightGain = std::max(maxGain(node->right), 0);
// The price to start a new path where `node` is the highest node
int priceNewPath = node->val + leftGain + rightGain;
// Update the maximum sum if the new path is better
maxSum = std::max(maxSum, priceNewPath);
// Return the maximum gain if continuing the same path
return node->val + std::max(leftGain, rightGain);
}
};
package main
import "math"
func maxPathSum(root *TreeNode) int {
maxSum := math.MinInt32
var maxGain func(*TreeNode) int
maxGain = func(node *TreeNode) int {
if node == nil {
return 0
}
// 递归计算左右子节点的最大贡献值
// 只有在最大贡献值大于 0 时,才会选取对应子节点
leftGain := max(maxGain(node.Left), 0)
rightGain := max(maxGain(node.Right), 0)
// 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
priceNewPath := node.Val + leftGain + rightGain
// 更新答案
maxSum = max(maxSum, priceNewPath)
// 返回节点的最大贡献值
return node.Val + max(leftGain, rightGain)
}
maxGain(root)
return maxSum
}
52、
剑指 Offer II 052. 展平二叉搜索树
给定一个二叉搜索树,将其进行结构转换,使得没有左节点,只有右节点
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
TreeNode *increasingBST(TreeNode *root) {
TreeNode *dummyNode = new TreeNode(0);
TreeNode *resNode = dummyNode;
inorder(root, resNode);
return dummyNode->right;
}
private:
void inorder(TreeNode *node, TreeNode *&resNode) {
if (!node) {
return;
}
inorder(node->left, resNode);
// Modify the node pointers during in-order traversal
resNode->right = node;
node->left = nullptr;
resNode = node;
inorder(node->right, resNode);
}
};
package main
func increasingBST(root *TreeNode) *TreeNode {
dummyNode := &TreeNode{}
resNode := dummyNode
var inorder func(*TreeNode)
inorder = func(node *TreeNode) {
if node == nil {
return
}
inorder(node.Left)
// 在中序遍历的过程中修改节点指向
resNode.Right = node
node.Left = nil
resNode = node
inorder(node.Right)
}
inorder(root)
return dummyNode.Right
}
53、
剑指 Offer II 053. 二叉搜索树中的中序后继
给定一个二叉搜索树以及一个节点,找到这个节点的中序后继
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
TreeNode *inorderSuccessor(TreeNode *root, TreeNode *p) {
TreeNode *successor = nullptr;
if (p->right) {
successor = p->right;
while (successor->left) {
successor = successor->left;
}
return successor;
}
TreeNode *node = root;
while (node) {
if (node->val > p->val) {
successor = node;
node = node->left;
} else {
node = node->right;
}
}
return successor;
}
};
package main
func inorderSuccessor(root *TreeNode, p *TreeNode) *TreeNode {
var successor *TreeNode
if p.Right != nil {
successor = p.Right
for successor.Left != nil {
successor = successor.Left
}
return successor
}
node := root
for node != nil {
if node.Val > p.Val {
successor = node
node = node.Left
} else {
node = node.Right
}
}
return successor
}
54、
剑指 Offer II 054. 所有大于等于节点的值之和
给定一个二叉搜索树,将每个节点替换成大于他节点的元素的和
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
TreeNode *convertBST(TreeNode *root) {
int sum = 0;
dfs(root, sum);
return root;
}
private:
void dfs(TreeNode *node, int &sum) {
if (node != nullptr) {
dfs(node->right, sum);
sum += node->val;
node->val = sum;
dfs(node->left, sum);
}
}
};
package main
func convertBST(root *TreeNode) *TreeNode {
sum := 0
var dfs func(*TreeNode)
dfs = func(node *TreeNode) {
if node != nil {
dfs(node.Right)
sum += node.Val
node.Val = sum
dfs(node.Left)
}
}
dfs(root)
return root
}
55、
剑指 Offer II 055. 二叉搜索树迭代器
给定一个二叉搜素树迭代器,提供next和hasNext两个方法
#include <stack>
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class BSTIterator {
public:
BSTIterator(TreeNode *root) : cur(root) {}
int next() {
while (cur) {
stack.push(cur);
cur = cur->left;
}
cur = stack.top();
stack.pop();
int val = cur->val;
cur = cur->right;
return val;
}
bool hasNext() { return cur != nullptr || !stack.empty(); }
private:
std::stack<TreeNode *> stack;
TreeNode *cur;
};
package main
type BSTIterator struct {
stack []*TreeNode
cur *TreeNode
}
func NewBSTIterator(root *TreeNode) BSTIterator {
return BSTIterator{cur: root}
}
func (it *BSTIterator) Next() int {
for node := it.cur; node != nil; node = node.Left {
it.stack = append(it.stack, node)
}
it.cur, it.stack = it.stack[len(it.stack)-1], it.stack[:len(it.stack)-1]
val := it.cur.Val
it.cur = it.cur.Right
return val
}
func (it *BSTIterator) HasNext() bool {
return it.cur != nil || len(it.stack) > 0
}
56、
剑指 Offer II 056. 二叉搜索树中两个节点之和
判断二叉搜索树中是否存在两个节点,和为目标值
#include <unordered_set>
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
bool findTarget(TreeNode *root, int k) {
std::unordered_set<int> visit;
bool flag = false;
inorder(root, k, visit, flag);
return flag;
}
private:
void inorder(TreeNode *node, int k, std::unordered_set<int> &visit,
bool &flag) {
if (!node || flag) {
return;
}
inorder(node->left, k, visit, flag);
if (visit.count(k - node->val)) {
flag = true;
return;
}
visit.insert(node->val);
inorder(node->right, k, visit, flag);
}
};
package main
func findTarget(root *TreeNode, k int) bool {
visit := map[int]struct{}{}
flag := false
var inorder func(*TreeNode)
inorder = func(node *TreeNode) {
if node == nil || flag {
return
}
inorder(node.Left)
if _, ok := visit[k-node.Val]; ok {
flag = true
return
}
visit[node.Val] = struct{}{}
inorder(node.Right)
}
inorder(root)
return flag
}
57、
剑指 Offer II 057. 值和下标之差都在给定的范围内
给定一个数组,判断是否存在两个下标i、j,下标差绝对值小于等于k,所对应的值差值小于等于t
#include <cmath>
#include <unordered_map>
#include <vector>
int getID(int x, int w) {
if (x >= 0) {
return x / w;
}
return (x + 1) / w - 1;
}
bool containsNearbyAlmostDuplicate(const std::vector<int> &nums, int k, int t) {
std::unordered_map<int, int> mp;
for (int i = 0; i < nums.size(); ++i) {
int x = nums[i];
int id = getID(x, t + 1);
if (mp.count(id)) {
return true;
}
if (mp.count(id - 1) && std::abs(x - mp[id - 1]) <= t) {
return true;
}
if (mp.count(id + 1) && std::abs(x - mp[id + 1]) <= t) {
return true;
}
mp[id] = x;
if (i >= k) {
mp.erase(getID(nums[i - k], t + 1));
}
}
return false;
}
int abs(int x) { return x < 0 ? -x : x; }
package main
func getID(x, w int) int {
if x >= 0 {
return x / w
}
return (x+1)/w - 1
}
func containsNearbyAlmostDuplicate(nums []int, k, t int) bool {
mp := map[int]int{}
for i, x := range nums {
id := getID(x, t+1)
if _, has := mp[id]; has {
return true
}
if y, has := mp[id-1]; has && abs(x-y) <= t {
return true
}
if y, has := mp[id+1]; has && abs(x-y) <= t {
return true
}
mp[id] = x
if i >= k {
delete(mp, getID(nums[i-k], t+1))
}
}
return false
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
58、
剑指 Offer II 058. 日程表
提供一个方法,在添加日程的时候,时间区间为[start, end)
, 只要这个时间段没有其他安排就能够使用
#include <map>
class MyCalendar {
public:
MyCalendar() {}
bool book(int start, int end) {
if (query(start, end - 1, 0, 1e9, 1)) {
return false;
}
update(start, end - 1, 0, 1e9, 1);
return true;
}
private:
std::map<int, bool> tree, lazy;
bool query(int start, int end, int l, int r, int idx) {
if (r < start || end < l) {
return false;
}
if (lazy[idx]) { // If the interval is already booked, return true
return true;
}
if (start <= l && r <= end) {
return tree[idx];
}
int mid = (l + r) >> 1;
return query(start, end, l, mid, 2 * idx) ||
query(start, end, mid + 1, r, 2 * idx + 1);
}
void update(int start, int end, int l, int r, int idx) {
if (r < start || end < l) {
return;
}
if (start <= l && r <= end) {
tree[idx] = true;
lazy[idx] = true;
} else {
int mid = (l + r) >> 1;
update(start, end, l, mid, 2 * idx);
update(start, end, mid + 1, r, 2 * idx + 1);
tree[idx] = true;
if (lazy[2 * idx] && lazy[2 * idx + 1]) {
lazy[idx] = true;
}
}
}
};
package main
type MyCalendar struct {
tree, lazy map[int]bool
}
func NewMyCalendar() MyCalendar {
return MyCalendar{map[int]bool{}, map[int]bool{}}
}
func (c MyCalendar) query(start, end, l, r, idx int) bool {
if r < start || end < l {
return false
}
if c.lazy[idx] { // 如果该区间已被预订,则直接返回
return true
}
if start <= l && r <= end {
return c.tree[idx]
}
mid := (l + r) >> 1
return c.query(start, end, l, mid, 2*idx) ||
c.query(start, end, mid+1, r, 2*idx+1)
}
func (c MyCalendar) update(start, end, l, r, idx int) {
if r < start || end < l {
return
}
if start <= l && r <= end {
c.tree[idx] = true
c.lazy[idx] = true
} else {
mid := (l + r) >> 1
c.update(start, end, l, mid, 2*idx)
c.update(start, end, mid+1, r, 2*idx+1)
c.tree[idx] = true
if c.lazy[2*idx] && c.lazy[2*idx+1] {
c.lazy[idx] = true
}
}
}
func (c MyCalendar) Book(start, end int) bool {
if c.query(start, end-1, 0, 1e9, 1) {
return false
}
c.update(start, end-1, 0, 1e9, 1)
return true
}
59、
剑指 Offer II 059. 数据流的第 K 大数值
一个数据流结构,在插入元素的时候返回数据中第k大的数。
#include <functional>
#include <queue>
#include <vector>
class KthLargest {
public:
KthLargest(int k, std::vector<int> &nums) : k(k) {
for (int val : nums) {
add(val);
}
}
int add(int val) {
if (minHeap.size() < k) {
minHeap.push(val);
} else if (val > minHeap.top()) {
minHeap.push(val);
if (minHeap.size() > k) {
minHeap.pop();
}
}
return minHeap.top();
}
private:
int k;
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
};
package main
import (
"container/heap"
"sort"
)
type KthLargest struct {
sort.IntSlice
k int
}
func NewKthLargest(k int, nums []int) KthLargest {
kl := KthLargest{k: k}
for _, val := range nums {
kl.Add(val)
}
return kl
}
func (kl *KthLargest) Push(v interface{}) {
kl.IntSlice = append(kl.IntSlice, v.(int))
}
func (kl *KthLargest) Pop() interface{} {
a := kl.IntSlice
v := a[len(a)-1]
kl.IntSlice = a[:len(a)-1]
return v
}
func (kl *KthLargest) Add(val int) int {
heap.Push(kl, val)
if kl.Len() > kl.k {
heap.Pop(kl)
}
return kl.IntSlice[0]
}
60、
剑指 Offer II 060. 出现频率最高的 k 个数字
统计数字数组中元素的出现次数,返回出现频率最高的几个
#include <queue>
#include <unordered_map>
#include <utility>
#include <vector>
class IHeap {
public:
bool operator()(const std::pair<int, int> &a, const std::pair<int, int> &b) {
return a.second > b.second;
}
};
std::vector<int> topKFrequent(std::vector<int> &nums, int k) {
std::unordered_map<int, int> occurrences;
for (int num : nums) {
occurrences[num]++;
}
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>,
IHeap>
h;
for (const auto &[key, value] : occurrences) {
h.emplace(key, value);
if (h.size() > k) {
h.pop();
}
}
std::vector<int> ret(k);
for (int i = k - 1; i >= 0; --i) {
ret[i] = h.top().first;
h.pop();
}
return ret;
}
package main
import "container/heap"
func topKFrequent(nums []int, k int) []int {
occurrences := map[int]int{}
for _, num := range nums {
occurrences[num]++
}
h := &IHeap{}
heap.Init(h)
for key, value := range occurrences {
heap.Push(h, [2]int{key, value})
if h.Len() > k {
heap.Pop(h)
}
}
ret := make([]int, k)
for i := 0; i < k; i++ {
ret[k-i-1] = heap.Pop(h).([2]int)[0]
}
return ret
}
type IHeap [][2]int
func (h IHeap) Len() int { return len(h) }
func (h IHeap) Less(i, j int) bool { return h[i][1] < h[j][1] }
func (h IHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IHeap) Push(x interface{}) {
*h = append(*h, x.([2]int))
}
func (h *IHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
61、
剑指 Offer II 061. 和最小的 k 个数对
给定两个升序数组,找到前n小的数对
#include <queue>
#include <tuple>
#include <vector>
using namespace std;
vector<vector<int>> kSmallestPairs(vector<int> &nums1, vector<int> &nums2,
int k) {
vector<vector<int>> ans;
auto comp = [](const pair<int, int> &a, const pair<int, int> &b) {
return a.first + a.second > b.first + b.second;
};
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(comp)>
minHeap(comp);
int m = nums1.size(), n = nums2.size();
for (int i = 0; i < k && i < m; ++i) {
minHeap.emplace(i, 0);
}
while (!minHeap.empty() && ans.size() < k) {
auto [i, j] = minHeap.top();
minHeap.pop();
ans.push_back({nums1[i], nums2[j]});
if (j + 1 < n) {
minHeap.emplace(i, j + 1);
}
}
return ans;
}
package main
import "container/heap"
func kSmallestPairs(nums1, nums2 []int, k int) (ans [][]int) {
h := hp{nums1: nums1, nums2: nums2}
m, n := len(nums1), len(nums2)
for i := 0; i < k && i < m; i++ {
heap.Push(&h, pair{i, 0})
}
for h.Len() > 0 && len(ans) < k {
p := heap.Pop(&h).(pair)
i, j := p.i, p.j
ans = append(ans, []int{nums1[i], nums2[j]})
if j+1 < n {
heap.Push(&h, pair{i, j + 1})
}
}
return
}
type hp struct {
data []pair
nums1, nums2 []int
}
type pair struct {
i, j int
}
func (h hp) Len() int {
return len(h.data)
}
func (h hp) Less(i, j int) bool {
p1, p2 := h.data[i], h.data[j]
return h.nums1[p1.i]+h.nums2[p1.j] < h.nums1[p2.i]+h.nums2[p2.j]
}
func (h hp) Swap(i, j int) {
h.data[i], h.data[j] = h.data[j], h.data[i]
}
func (h *hp) Push(val interface{}) {
h.data = append(h.data, val.(pair))
}
func (h *hp) Pop() interface{} {
data := h.data
val := data[len(data)-1]
h.data = data[:len(data)-1]
return val
}
62、
剑指 Offer II 062. 实现前缀树
设计一个前缀树,提供数据插入,数据搜索,判断是否存在某个前缀
#include <string>
#include <vector>
class Trie {
public:
Trie() : children(26, nullptr), isEnd(false) {}
void Insert(const std::string &word) {
Trie *node = this;
for (char ch : word) {
ch -= 'a';
if (node->children[ch] == nullptr) {
node->children[ch] = new Trie();
}
node = node->children[ch];
}
node->isEnd = true;
}
bool Search(const std::string &word) {
Trie *node = SearchPrefix(word);
return node != nullptr && node->isEnd;
}
bool StartsWith(const std::string &prefix) {
return SearchPrefix(prefix) != nullptr;
}
private:
std::vector<Trie *> children;
bool isEnd;
Trie *SearchPrefix(const std::string &prefix) {
Trie *node = this;
for (char ch : prefix) {
ch -= 'a';
if (node->children[ch] == nullptr) {
return nullptr;
}
node = node->children[ch];
}
return node;
}
};
package main
type Trie struct {
children [26]*Trie
isEnd bool
}
func Constructor() Trie {
return Trie{}
}
func (t *Trie) Insert(word string) {
node := t
for _, ch := range word {
ch -= 'a'
if node.children[ch] == nil {
node.children[ch] = &Trie{}
}
node = node.children[ch]
}
node.isEnd = true
}
func (t *Trie) SearchPrefix(prefix string) *Trie {
node := t
for _, ch := range prefix {
ch -= 'a'
if node.children[ch] == nil {
return nil
}
node = node.children[ch]
}
return node
}
func (t *Trie) Search(word string) bool {
node := t.SearchPrefix(word)
return node != nil && node.isEnd
}
func (t *Trie) StartsWith(prefix string) bool {
return t.SearchPrefix(prefix) != nil
}
63、
剑指 Offer II 063. 替换单词
当一个词为另一个词的前缀那么称其为词根,后者称为继承词
给定一个词根数组,一个句子,将句子中的所有词语,如果存在其词根,那么就用词根进行替换
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
using namespace std;
class Trie {
public:
unordered_map<char, Trie *> children;
bool isEnd = false;
void insert(const string &word) {
Trie *node = this;
for (char c : word) {
if (node->children.find(c) == node->children.end()) {
node->children[c] = new Trie();
}
node = node->children[c];
}
node->isEnd = true;
}
string searchRoot(const string &word) {
Trie *node = this;
for (int i = 0; i < word.size(); ++i) {
char c = word[i];
if (node->isEnd) {
return word.substr(0, i);
}
if (node->children.find(c) == node->children.end()) {
break;
}
node = node->children[c];
}
return word;
}
};
string replaceWords(vector<string> &dictionary, const string &sentence) {
Trie root;
for (const string &word : dictionary) {
root.insert(word);
}
istringstream iss(sentence);
string word;
string result;
while (iss >> word) {
if (!result.empty()) {
result += " ";
}
result += root.searchRoot(word);
}
return result;
}
package main
import "strings"
func replaceWords(dictionary []string, sentence string) string {
type trie map[rune]trie
root := trie{}
for _, s := range dictionary {
cur := root
for _, c := range s {
if cur[c] == nil {
cur[c] = trie{}
}
cur = cur[c]
}
cur['#'] = trie{}
}
words := strings.Split(sentence, " ")
for i, word := range words {
cur := root
for j, c := range word {
if cur['#'] != nil {
words[i] = word[:j]
break
}
if cur[c] == nil {
break
}
cur = cur[c]
}
}
return strings.Join(words, " ")
}
64、
剑指 Offer II 064. 神奇的字典
一个初始字符串列表,提供一个搜索方法,判断需要搜索的字符串进行一次字符替换后是否出现在初始字符串列表。
#include <string>
#include <vector>
class Trie {
public:
Trie *children[26] = {nullptr};
bool isEnd = false;
};
class MagicDictionary {
public:
MagicDictionary() { root = new Trie(); }
void BuildDict(const std::vector<std::string> &dictionary) {
for (const std::string &word : dictionary) {
Trie *node = root;
for (char ch : word) {
int idx = ch - 'a';
if (node->children[idx] == nullptr) {
node->children[idx] = new Trie();
}
node = node->children[idx];
}
node->isEnd = true;
}
}
bool Search(const std::string &searchWord) {
return dfs(root, searchWord, 0, false);
}
private:
Trie *root;
bool dfs(Trie *node, const std::string &word, int index, bool modified) {
if (index == word.size()) {
return modified && node->isEnd;
}
int c = word[index] - 'a';
if (node->children[c] != nullptr &&
dfs(node->children[c], word, index + 1, modified)) {
return true;
}
if (!modified) {
for (int i = 0; i < 26; ++i) {
if (i != c && node->children[i] != nullptr &&
dfs(node->children[i], word, index + 1, true)) {
return true;
}
}
}
return false;
}
};
package main
type MagicDictionary struct {
trie *trie
}
type trie struct {
children [26]*trie
isEnd bool
}
/** Initialize your data structure here. */
func NewMagicDictionary() MagicDictionary {
return MagicDictionary{trie: &trie{}}
}
func (this *MagicDictionary) BuildDict(dictionary []string) {
for _, word := range dictionary {
cur := this.trie
for _, ch := range word {
idx := ch - 'a'
if cur.children[idx] == nil {
cur.children[idx] = &trie{}
}
cur = cur.children[idx]
}
cur.isEnd = true
}
}
func (this *MagicDictionary) Search(searchWord string) bool {
var dfs func(node *trie, searchWord string, modified bool) bool
dfs = func(node *trie, searchWord string, modified bool) bool {
if searchWord == "" {
return modified && node.isEnd
}
c := searchWord[0] - 'a'
if node.children[c] != nil && dfs(node.children[c], searchWord[1:], modified) {
return true
}
if !modified {
for i, child := range node.children {
if i != int(c) && child != nil && dfs(child, searchWord[1:], true) {
return true
}
}
}
return false
}
return dfs(this.trie, searchWord, false)
}
65、
剑指 Offer II 065. 最短的单词编码
暂时没理解到含义,详情查看https://leetcode.cn/problems/iSwD2y/?favorite=e8X3pBZi
#include <algorithm>
#include <functional>
#include <string>
#include <vector>
class Trie {
public:
Trie *child[26] = {nullptr};
int depth = 1;
Trie() = default;
void Insert(const std::string &word) {
Trie *curr = this;
int n = word.size();
for (int i = n - 1; i >= 0; --i) {
int c = word[i] - 'a';
if (curr->child[c] == nullptr) {
curr->child[c] = new Trie();
}
curr->child[c]->depth = curr->depth + 1;
curr = curr->child[c];
}
}
};
int minimumLengthEncoding(std::vector<std::string> &words) {
Trie *root = new Trie();
for (const std::string &word : words) {
root->Insert(word);
}
int res = 0;
std::function<void(Trie *)> dfs = [&](Trie *node) -> void {
bool hasChild = false;
for (int i = 0; i < 26; ++i) {
if (node->child[i] != nullptr) {
hasChild = true;
dfs(node->child[i]);
}
}
if (!hasChild) {
res += node->depth;
}
};
dfs(root);
return res;
}
package main
type WordEncodeTrie struct {
child [26]*WordEncodeTrie
depth int
}
func NewTrie() *WordEncodeTrie {
return &WordEncodeTrie{
child: [26]*WordEncodeTrie{},
depth: 1,
}
}
func (t *WordEncodeTrie) Insert(word string) {
curr := t
n := len(word)
for i := n - 1; i >= 0; i-- {
c := word[i] - 'a'
if curr.child[c] == nil {
curr.child[c] = NewTrie()
}
curr.child[c].depth = curr.depth + 1
curr = curr.child[c]
}
}
func minimumLengthEncoding(words []string) int {
t := NewTrie()
for _, w := range words {
t.Insert(w)
}
var res int
var dfs func(node *WordEncodeTrie)
dfs = func(node *WordEncodeTrie) {
var hasChild bool
for i := 0; i < 26; i++ {
if node.child[i] != nil {
hasChild = true
dfs(node.child[i])
}
}
if !hasChild {
res += node.depth
}
}
dfs(t)
return res
}
66、
剑指 Offer II 066. 单词之和
提供两个方法,插入字符串以及个数,查询一个前缀所包含的值的大小。
如果该key已经存在则进行替换,给定一个字符串查询以该字符串为前缀的元素总和
#include <string>
#include <unordered_map>
#include <vector>
class TrieNode {
public:
TrieNode *children[26] = {nullptr};
int val = 0;
};
class MapSum {
public:
MapSum() { root = new TrieNode(); }
void Insert(const std::string &key, int val) {
int delta = val;
if (cnt.find(key) != cnt.end()) {
delta -= cnt[key];
}
cnt[key] = val;
TrieNode *node = root;
for (char ch : key) {
ch -= 'a';
if (node->children[ch] == nullptr) {
node->children[ch] = new TrieNode();
}
node = node->children[ch];
node->val += delta;
}
}
int Sum(const std::string &prefix) {
TrieNode *node = root;
for (char ch : prefix) {
ch -= 'a';
if (node->children[ch] == nullptr) {
return 0;
}
node = node->children[ch];
}
return node->val;
}
private:
TrieNode *root;
std::unordered_map<std::string, int> cnt;
};
package main
type TrieNode struct {
children [26]*TrieNode
val int
}
type MapSum struct {
root *TrieNode
cnt map[string]int
}
func NewMapSum() MapSum {
return MapSum{
&TrieNode{},
map[string]int{},
}
}
func (m *MapSum) Insert(key string, val int) {
delta := val
if m.cnt[key] > 0 {
delta -= m.cnt[key]
}
m.cnt[key] = val
node := m.root
for _, ch := range key {
ch -= 'a'
if node.children[ch] == nil {
node.children[ch] = &TrieNode{}
}
node = node.children[ch]
node.val += delta
}
}
func (m *MapSum) Sum(prefix string) int {
node := m.root
for _, ch := range prefix {
ch -= 'a'
if node.children[ch] == nil {
return 0
}
node = node.children[ch]
}
return node.val
}
67、
剑指 Offer II 067. 最大的异或
给定一个整数数组,两两元素进行异或,找到最大值
#include <algorithm>
#include <vector>
const int highBit = 30;
class Trie {
public:
Trie *left = nullptr;
Trie *right = nullptr;
void add(int num) {
Trie *cur = this;
for (int i = highBit; i >= 0; --i) {
int bit = (num >> i) & 1;
if (bit == 0) {
if (cur->left == nullptr) {
cur->left = new Trie();
}
cur = cur->left;
} else {
if (cur->right == nullptr) {
cur->right = new Trie();
}
cur = cur->right;
}
}
}
int check(int num) {
Trie *cur = this;
int x = 0;
for (int i = highBit; i >= 0; --i) {
int bit = (num >> i) & 1;
if (bit == 0) {
if (cur->right != nullptr) {
cur = cur->right;
x = x * 2 + 1;
} else {
cur = cur->left;
x = x * 2;
}
} else {
if (cur->left != nullptr) {
cur = cur->left;
x = x * 2 + 1;
} else {
cur = cur->right;
x = x * 2;
}
}
}
return x;
}
};
int findMaximumXOR(const std::vector<int> &nums) {
Trie *root = new Trie();
int x = 0;
for (int i = 1; i < nums.size(); ++i) {
root->add(nums[i - 1]);
x = std::max(x, root->check(nums[i]));
}
return x;
}
package main
const highBit = 30
type xor_trie struct {
left, right *xor_trie
}
func (t *xor_trie) add(num int) {
cur := t
for i := highBit; i >= 0; i-- {
bit := num >> i & 1
if bit == 0 {
if cur.left == nil {
cur.left = &xor_trie{}
}
cur = cur.left
} else {
if cur.right == nil {
cur.right = &xor_trie{}
}
cur = cur.right
}
}
}
func (t *xor_trie) check(num int) (x int) {
cur := t
for i := highBit; i >= 0; i-- {
bit := num >> i & 1
if bit == 0 {
// a_i 的第 k 个二进制位为 0,应当往表示 1 的子节点 right 走
if cur.right != nil {
cur = cur.right
x = x*2 + 1
} else {
cur = cur.left
x = x * 2
}
} else {
// a_i 的第 k 个二进制位为 1,应当往表示 0 的子节点 left 走
if cur.left != nil {
cur = cur.left
x = x*2 + 1
} else {
cur = cur.right
x = x * 2
}
}
}
return
}
func findMaximumXOR(nums []int) (x int) {
root := &xor_trie{}
for i := 1; i < len(nums); i++ {
// 将 nums[i-1] 放入字典树,此时 nums[0 .. i-1] 都在字典树中
root.add(nums[i-1])
// 将 nums[i] 看作 ai,找出最大的 x 更新答案
x = max(x, root.check(nums[i]))
}
return
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
68、
剑指 Offer II 068. 查找插入位置
往一个有序数组中进行数据插入,查找数据的插入位置,要求使用O(logn)复杂度
#include <vector>
int searchInsert(const std::vector<int> &nums, int target) {
int n = nums.size();
int left = 0, right = n - 1;
int ans = n;
while (left <= right) {
int mid = (right - left) / 2 + left;
if (target <= nums[mid]) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return ans;
}
package main
func searchInsert(nums []int, target int) int {
n := len(nums)
left, right := 0, n-1
ans := n
for left <= right {
mid := (right-left)>>1 + left
if target <= nums[mid] {
ans = mid
right = mid - 1
} else {
left = mid + 1
}
}
return ans
}
69、
剑指 Offer II 069. 山峰数组的顶部
找到下标i,在i之前是递增数据,在i之后是递减数据
题目保证满足存在这样的i
#include <algorithm>
#include <vector>
int peakIndexInMountainArray(const std::vector<int> &arr) {
return std::distance(arr.begin(), std::find_if(
arr.begin(), arr.end() - 1,
&{ return arr[i] > arr[i + 1]; }));
}
package main
import "sort"
func peakIndexInMountainArray(arr []int) int {
return sort.Search(len(arr)-1, func(i int) bool { return arr[i] > arr[i+1] })
}
70、
剑指 Offer II 070. 排序数组中只出现一次的数字
一个有序数组,有一个元素只出现一次,其他元素出现了两次,找到这个只出现了一次的元素,满足O(logn)时间复杂度,O(1)空间复杂度
#include <vector>
int singleNonDuplicate(const std::vector<int> &nums) {
int low = 0, high = nums.size() - 1;
while (low < high) {
int mid = low + (high - low) / 2;
if (nums[mid] == nums[mid ^ 1]) {
low = mid + 1;
} else {
high = mid;
}
}
return nums[low];
}
package main
func singleNonDuplicate(nums []int) int {
low, high := 0, len(nums)-1
for low < high {
mid := low + (high-low)/2
if nums[mid] == nums[mid^1] {
low = mid + 1
} else {
high = mid
}
}
return nums[low]
}
71、
剑指 Offer II 071. 按权重生成随机数
按照值的大小作为权重占比进行随机数获取,返回其数据下标
#include <algorithm>
#include <cstdlib>
#include <vector>
class Solution {
public:
Solution(std::vector<int> &w) {
for (int i = 1; i < w.size(); ++i) {
w[i] += w[i - 1];
}
pre = w;
}
int PickIndex() {
int x = rand() % pre.back() + 1;
return std::lower_bound(pre.begin(), pre.end(), x) - pre.begin();
}
private:
std::vector<int> pre;
};
package main
import (
"math/rand"
"sort"
)
type WeightRandom struct {
pre []int
}
func NewWeightRandom(w []int) WeightRandom {
for i := 1; i < len(w); i++ {
w[i] += w[i-1]
}
return WeightRandom{w}
}
func (s *WeightRandom) PickIndex() int {
x := rand.Intn(s.pre[len(s.pre)-1]) + 1
return sort.SearchInts(s.pre, x)
}
72、
剑指 Offer II 072. 求平方根
计算一个整数的开平方根,只取正数那一个
int mySqrt(int x) {
int l = 0, r = x;
int ans = -1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (mid * mid <= x) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
return ans;
}
package main
func mySqrt(x int) int {
l, r := 0, x
ans := -1
for l <= r {
mid := l + (r-l)/2
if mid*mid <= x {
ans = mid
l = mid + 1
} else {
r = mid - 1
}
}
return ans
}
73、
剑指 Offer II 073. 狒狒吃香蕉
n堆香蕉,在h小时内要吃完,选择要吃完所有香蕉的最小速度,不过需要注意如果选择了一堆香蕉后,小于k个也会等待下一个小时才会继续吃。
#include <algorithm>
#include <vector>
int minEatingSpeed(const std::vector<int> &piles, int h) {
int maxPile = *std::max_element(piles.begin(), piles.end());
return 1 + std::lower_bound(1, maxPile - 1, [&](int speed) -> bool {
int time = 0;
for (int pile : piles) {
time += (pile + speed - 1) / speed;
}
return time <= h;
});
}
package main
import "sort"
func minEatingSpeed(piles []int, h int) int {
max := 0
for _, pile := range piles {
if pile > max {
max = pile
}
}
return 1 + sort.Search(max-1, func(speed int) bool {
speed++
time := 0
for _, pile := range piles {
time += (pile + speed - 1) / speed
}
return time <= h
})
}
74、
剑指 Offer II 074. 合并区间
给定一个元组数组,其中的元素即为一个左右区间,将所有重叠的区间进行合并,并返回一个不重叠的区间数组
#include <algorithm>
#include <vector>
using namespace std;
vector<vector<int>> merge(vector<vector<int>> &intervals) {
sort(intervals.begin(), intervals.end(),
[](const vector<int> &a, const vector<int> &b) {
return a[0] < b[0] || (a[0] == b[0] && a[1] < b[1]);
});
vector<vector<int>> res;
for (const auto &interval : intervals) {
if (res.empty() || res.back()[1] < interval[0]) {
res.push_back(interval);
} else {
res.back()[1] = max(res.back()[1], interval[1]);
}
}
return res;
}
int max(int a, int b) { return (a > b) ? a : b; }
package main
import "sort"
func merge(intervals [][]int) [][]int {
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0] || (intervals[i][0] == intervals[j][0] && intervals[i][1] < intervals[j][1])
})
var res [][]int
for _, interval := range intervals {
if len(res) == 0 || res[len(res)-1][1] < interval[0] {
res = append(res, interval)
} else {
res[len(res)-1][1] = max(res[len(res)-1][1], interval[1])
}
}
return res
}
75
剑指 Offer II 075. 数组相对排序
给定两个数组arr1和arr2,将arr1按照arr2的顺序进行排序,未出现过的就按照升序之后放在arr1的末尾。
#include <algorithm>
#include <unordered_map>
#include <vector>
using namespace std;
class ZSet {
public:
void insert(int key) { data[key]++; }
void erase(int key) { data.erase(key); }
vector<int> keys() const {
vector<int> res;
for (const auto &[key, _] : data) {
res.push_back(key);
}
sort(res.begin(), res.end());
return res;
}
int count(int key) const {
auto it = data.find(key);
return it != data.end() ? it->second : 0;
}
private:
unordered_map<int, int> data;
};
vector<int> relativeSortArray(vector<int> &arr1, const vector<int> &arr2) {
ZSet zs;
for (int val : arr1) {
zs.insert(val);
}
vector<int> res;
for (int val : arr2) {
int count = zs.count(val);
res.insert(res.end(), count, val);
zs.erase(val);
}
vector<int> remainingKeys = zs.keys();
for (int key : remainingKeys) {
int count = zs.count(key);
res.insert(res.end(), count, key);
}
return res;
}
package main
import "sort"
type zset map[int]int
func (zs *zset) keys() []int {
res := []int{}
for key := range *zs {
res = append(res, key)
}
sort.Ints(res)
return res
}
func relativeSortArray(arr1 []int, arr2 []int) []int {
zs := zset{}
for _, val := range arr1 {
zs[val]++
}
res := make([]int, 0, len(arr1))
for _, val := range arr2 {
for i := 0; i < zs[val]; i++ {
res = append(res, val)
}
delete(zs, val)
}
for _, key := range zs.keys() {
for i := 0; i < zs[key]; i++ {
res = append(res, key)
}
}
return res
}
76
剑指 Offer II 076. 数组中的第 k 大的数字
给定一个整数数组和整数,返回数组中第k个最大的元素
#include <cstdlib>
#include <ctime>
#include <vector>
using namespace std;
int partition(vector<int> &a, int l, int r) {
int x = a[r];
int i = l - 1;
for (int j = l; j < r; ++j) {
if (a[j] <= x) {
++i;
swap(a[i], a[j]);
}
}
swap(a[i + 1], a[r]);
return i + 1;
}
int randomPartition(vector<int> &a, int l, int r) {
int i = rand() % (r - l + 1) + l;
swap(a[i], a[r]);
return partition(a, l, r);
}
int quickSelect(vector<int> &a, int l, int r, int index) {
int q = randomPartition(a, l, r);
if (q == index) {
return a[q];
} else if (q < index) {
return quickSelect(a, q + 1, r, index);
}
return quickSelect(a, l, q - 1, index);
}
int findKthLargest(vector<int> &nums, int k) {
srand(time(0));
return quickSelect(nums, 0, nums.size() - 1, nums.size() - k);
}
package main
import (
"math/rand"
"time"
)
func findKthLargest(nums []int, k int) int {
rand.Seed(time.Now().UnixNano())
return quickSelect(nums, 0, len(nums)-1, len(nums)-k)
}
func quickSelect(a []int, l, r, index int) int {
q := randomPartition(a, l, r)
if q == index {
return a[q]
} else if q < index {
return quickSelect(a, q+1, r, index)
}
return quickSelect(a, l, q-1, index)
}
func randomPartition(a []int, l, r int) int {
i := rand.Int()%(r-l+1) + l
a[i], a[r] = a[r], a[i]
return partition(a, l, r)
}
func partition(a []int, l, r int) int {
x := a[r]
i := l - 1
for j := l; j < r; j++ {
if a[j] <= x {
i++
a[i], a[j] = a[j], a[i]
}
}
a[i+1], a[r] = a[r], a[i+1]
return i + 1
}
77
剑指 Offer II 077. 链表排序
对链表节点进行升序排序
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
ListNode *merge(ListNode *head1, ListNode *head2) {
ListNode dummyHead(0);
ListNode *temp = &dummyHead;
ListNode *temp1 = head1;
ListNode *temp2 = head2;
while (temp1 != nullptr && temp2 != nullptr) {
if (temp1->val <= temp2->val) {
temp->next = temp1;
temp1 = temp1->next;
} else {
temp->next = temp2;
temp2 = temp2->next;
}
temp = temp->next;
}
if (temp1 != nullptr) {
temp->next = temp1;
} else if (temp2 != nullptr) {
temp->next = temp2;
}
return dummyHead.next;
}
ListNode *sort(ListNode *head, ListNode *tail) {
if (head == nullptr) {
return head;
}
if (head->next == tail) {
head->next = nullptr;
return head;
}
ListNode *slow = head;
ListNode *fast = head;
while (fast != tail) {
slow = slow->next;
fast = fast->next;
if (fast != tail) {
fast = fast->next;
}
}
ListNode *mid = slow;
return merge(sort(head, mid), sort(mid, tail));
}
ListNode *sortList(ListNode *head) { return sort(head, nullptr); }
package main
type ListNode struct {
Val int
Next *ListNode
}
func MergeList(head1, head2 *ListNode) *ListNode {
dummyHead := &ListNode{}
temp, temp1, temp2 := dummyHead, head1, head2
for temp1 != nil && temp2 != nil {
if temp1.Val <= temp2.Val {
temp.Next = temp1
temp1 = temp1.Next
} else {
temp.Next = temp2
temp2 = temp2.Next
}
temp = temp.Next
}
if temp1 != nil {
temp.Next = temp1
} else if temp2 != nil {
temp.Next = temp2
}
return dummyHead.Next
}
func SortList(head, tail *ListNode) *ListNode {
if head == nil {
return head
}
if head.Next == tail {
head.Next = nil
return head
}
slow, fast := head, head
for fast != tail {
slow = slow.Next
fast = fast.Next
if fast != tail {
fast = fast.Next
}
}
mid := slow
return MergeList(SortList(head, mid), SortList(mid, tail))
}
func sortList(head *ListNode) *ListNode {
return SortList(head, nil)
}
78
剑指 Offer II 078. 合并排序链表
#include <queue>
#include <vector>
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
ListNode *mergeTwoLists(ListNode *list1, ListNode *list2) {
ListNode dummyHead(0);
ListNode *p = &dummyHead;
ListNode *p1 = list1;
ListNode *p2 = list2;
while (p1 != nullptr && p2 != nullptr) {
if (p1->val > p2->val) {
p->next = p2;
p2 = p2->next;
} else {
p->next = p1;
p1 = p1->next;
}
p = p->next;
}
if (p1 != nullptr) {
p->next = p1;
}
if (p2 != nullptr) {
p->next = p2;
}
return dummyHead.next;
}
ListNode *mergeKLists(std::vector<ListNode *> &lists) {
ListNode *res = nullptr;
for (ListNode *list : lists) {
res = mergeTwoLists(res, list);
}
return res;
}
package main
// 合并K个升序链表
func mergeKLists(lists []*ListNode) (res *ListNode) {
for _, v := range lists {
res = mergeTwoLists(res, v)
}
return res
}
// 合并两个升序链表
func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
//初始化一个虚拟的头节点
newList := &ListNode{}
p := newList
p1 := list1
p2 := list2
//遍历对比两个指针值的大小,有一个走完了就停止
for p1 != nil && p2 != nil {
//将值小的节点接到p指针后面
if p1.Val > p2.Val {
p.Next = p2
p2 = p2.Next
} else {
p.Next = p1
p1 = p1.Next
}
//p指针前进
p = p.Next
}
//将未比较的剩余节点都放到p指针后面
if p1 != nil {
p.Next = p1
}
if p2 != nil {
p.Next = p2
}
//返回虚拟头节点的下一个节点就是真正的头节点
return newList.Next
}
79
剑指 Offer II 079. 所有子集
给定一个元素各不相同的的数组,返回所有不重复的子集
#include <vector>
using namespace std;
vector<vector<int>> subsets(vector<int> &nums) {
vector<vector<int>> ans;
int n = nums.size();
for (int mask = 0; mask < (1 << n); ++mask) {
vector<int> set;
for (int i = 0; i < n; ++i) {
if (mask & (1 << i)) {
set.push_back(nums[i]);
}
}
ans.push_back(set);
}
return ans;
}
package main
func subsets(nums []int) (ans [][]int) {
n := len(nums)
for mask := 0; mask < 1<<n; mask++ {
set := []int{}
for i, v := range nums {
if mask>>i&1 > 0 {
set = append(set, v)
}
}
ans = append(ans, append([]int(nil), set...))
}
return
}
80
剑指 Offer II 080. 含有 k 个元素的组合
给定整数n和k,返回1...n
中所有可能的k个数的组合
#include <vector>
using namespace std;
void dfs(int cur, int n, int k, vector<int> &temp, vector<vector<int>> &ans) {
// Pruning: if the remaining elements plus the current temp size is less than
// k, return
if (temp.size() + (n - cur + 1) < k) {
return;
}
// Record valid answers
if (temp.size() == k) {
ans.push_back(temp);
return;
}
// Consider the current position
temp.push_back(cur);
dfs(cur + 1, n, k, temp, ans);
temp.pop_back();
// Consider not choosing the current position
dfs(cur + 1, n, k, temp, ans);
}
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> ans;
vector<int> temp;
dfs(1, n, k, temp, ans);
return ans;
}
package main
func combine(n int, k int) (ans [][]int) {
temp := []int{}
var dfs func(int)
dfs = func(cur int) {
// 剪枝:temp 长度加上区间 [cur, n] 的长度小于 k,不可能构造出长度为 k 的 temp
if len(temp)+(n-cur+1) < k {
return
}
// 记录合法的答案
if len(temp) == k {
comb := make([]int, k)
copy(comb, temp)
ans = append(ans, comb)
return
}
// 考虑选择当前位置
temp = append(temp, cur)
dfs(cur + 1)
temp = temp[:len(temp)-1]
// 考虑不选择当前位置
dfs(cur + 1)
}
dfs(1)
return
}
81
剑指 Offer II 081. 允许重复选择元素的组合
从数组中寻找和为目标值的组合,数组中的元素可以重复选择
下面这种题解是标准解法,不过不能去除重复的情况,详情看82
#include <vector>
using namespace std;
void dfs(const vector<int> &candidates, int target, int idx, vector<int> &comb,
vector<vector<int>> &ans) {
if (idx == candidates.size()) {
return;
}
if (target == 0) {
ans.push_back(comb);
return;
}
// Skip the current number
dfs(candidates, target, idx + 1, comb, ans);
// Choose the current number
if (target - candidates[idx] >= 0) {
comb.push_back(candidates[idx]);
dfs(candidates, target - candidates[idx], idx, comb, ans);
comb.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int> &candidates, int target) {
vector<vector<int>> ans;
vector<int> comb;
dfs(candidates, target, 0, comb, ans);
return ans;
}
package main
func combinationSum(candidates []int, target int) (ans [][]int) {
comb := []int{}
var dfs func(target, idx int)
dfs = func(target, idx int) {
if idx == len(candidates) {
return
}
if target == 0 {
ans = append(ans, append([]int(nil), comb...))
return
}
// 直接跳过
dfs(target, idx+1)
// 选择当前数
if target-candidates[idx] >= 0 {
comb = append(comb, candidates[idx])
dfs(target-candidates[idx], idx)
comb = comb[:len(comb)-1]
}
}
dfs(target, 0)
return
}
82
剑指 Offer II 082. 含有重复元素集合的组合
与81题类似,不过这里元素只能使用一次
#include <algorithm>
#include <vector>
using namespace std;
void dfs(int pos, int rest, const vector<pair<int, int>> &freq,
vector<int> &sequence, vector<vector<int>> &ans) {
if (rest == 0) {
ans.push_back(sequence);
return;
}
if (pos == freq.size() || rest < freq[pos].first) {
return;
}
dfs(pos + 1, rest, freq, sequence, ans);
int most = min(rest / freq[pos].first, freq[pos].second);
for (int i = 1; i <= most; ++i) {
sequence.push_back(freq[pos].first);
dfs(pos + 1, rest - i * freq[pos].first, freq, sequence, ans);
}
sequence.resize(sequence.size() - most);
}
vector<vector<int>> combinationSum2(vector<int> &candidates, int target) {
sort(candidates.begin(), candidates.end());
vector<pair<int, int>> freq;
for (int num : candidates) {
if (freq.empty() || num != freq.back().first) {
freq.emplace_back(num, 1);
} else {
++freq.back().second;
}
}
vector<vector<int>> ans;
vector<int> sequence;
dfs(0, target, freq, sequence, ans);
return ans;
}
int min(int a, int b) { return (a < b) ? a : b; }
package main
import "sort"
func combinationSum2(candidates []int, target int) (ans [][]int) {
sort.Ints(candidates)
var freq [][2]int // [值,次数]
for _, num := range candidates {
if freq == nil || num != freq[len(freq)-1][0] {
freq = append(freq, [2]int{num, 1})
} else {
freq[len(freq)-1][1]++
}
}
var sequence []int
var dfs func(pos, rest int)
dfs = func(pos, rest int) {
if rest == 0 {
ans = append(ans, append([]int(nil), sequence...))
return
}
if pos == len(freq) || rest < freq[pos][0] {
return
}
dfs(pos+1, rest)
most := min(rest/freq[pos][0], freq[pos][1])
for i := 1; i <= most; i++ {
sequence = append(sequence, freq[pos][0])
dfs(pos+1, rest-i*freq[pos][0])
}
sequence = sequence[:len(sequence)-most]
}
dfs(0, target)
return
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
83
剑指 Offer II 083. 没有重复元素集合的全排列
给定一个不包含重复数字的整数数组,返回所有可能的全排列
#include <vector>
using namespace std;
void dfs(vector<int> &nums, vector<bool> &vis, vector<int> &path,
vector<vector<int>> &ans) {
if (path.size() == nums.size()) {
ans.push_back(path);
return;
}
for (int i = 0; i < nums.size(); ++i) {
if (!vis[i]) {
vis[i] = true;
path.push_back(nums[i]);
dfs(nums, vis, path, ans);
vis[i] = false;
path.pop_back();
}
}
}
vector<vector<int>> permute(vector<int> &nums) {
vector<vector<int>> ans;
vector<int> path;
vector<bool> vis(nums.size(), false);
dfs(nums, vis, path, ans);
return ans;
}
package main
func permute(nums []int) [][]int {
n := len(nums)
var ans [][]int
var path []int
vis := make([]bool, n)
var dfs func()
dfs = func() {
if len(path) == n {
ans = append(ans, append([]int{}, path...))
return
}
for i := 0; i < n; i++ {
if !vis[i] {
vis[i] = true
path = append(path, nums[i])
dfs()
vis[i] = false
path = path[:len(path)-1]
}
}
}
dfs()
return ans
}
84
剑指 Offer II 084. 含有重复元素集合的全排列
给定一个可包含重复数字的整数集合 nums ,按任意顺序 返回它所有不重复的全排列。
#include <algorithm>
#include <vector>
using namespace std;
void backtrack(vector<int> &nums, vector<bool> &vis, vector<int> &perm,
vector<vector<int>> &ans, int idx) {
if (idx == nums.size()) {
ans.push_back(perm);
return;
}
for (int i = 0; i < nums.size(); ++i) {
if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) {
continue;
}
perm.push_back(nums[i]);
vis[i] = true;
backtrack(nums, vis, perm, ans, idx + 1);
vis[i] = false;
perm.pop_back();
}
}
vector<vector<int>> permuteUnique(vector<int> &nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
vector<int> perm;
vector<bool> vis(nums.size(), false);
backtrack(nums, vis, perm, ans, 0);
return ans;
}
package main
import "sort"
func permuteUnique(nums []int) (ans [][]int) {
sort.Ints(nums)
n := len(nums)
perm := []int{}
vis := make([]bool, n)
var backtrack func(int)
backtrack = func(idx int) {
if idx == n {
ans = append(ans, append([]int(nil), perm...))
return
}
for i, v := range nums {
if vis[i] || i > 0 && !vis[i-1] && v == nums[i-1] {
continue
}
perm = append(perm, v)
vis[i] = true
backtrack(idx + 1)
vis[i] = false
perm = perm[:len(perm)-1]
}
}
backtrack(0)
return
}
85
剑指 Offer II 085. 生成匹配的括号
正整数 n 代表生成括号的对数,请设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
#include <string>
#include <vector>
using namespace std;
vector<string> generateParenthesis(int n) {
vector<vector<string>> dp(n + 1);
dp[0] = {""};
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < i; ++j) {
for (const string &left : dp[j]) {
for (const string &right : dp[i - j - 1]) {
dp[i].push_back("(" + left + ")" + right);
}
}
}
}
return dp[n];
}
void dfs(int n, int leftCnt, int rightCnt, string &tmp, vector<string> &res) {
if (tmp.size() == n * 2) {
res.push_back(tmp);
return;
}
if (leftCnt < n) {
tmp.push_back('(');
dfs(n, leftCnt + 1, rightCnt, tmp, res);
tmp.pop_back();
}
if (leftCnt > rightCnt) {
tmp.push_back(')');
dfs(n, leftCnt, rightCnt + 1, tmp, res);
tmp.pop_back();
}
}
vector<string> generateParenthesisDFS(int n) {
vector<string> res;
string tmp;
dfs(n, 0, 0, tmp, res);
return res;
}
package main
// 动态规划版本
func generateParenthesisDP(n int) []string {
dp := make([][]string, n+1)
dp[0] = []string{""}
for i := 1; i <= n; i++ {
for j := 0; j < i; j++ {
for _, left := range dp[j] {
for _, right := range dp[i-j-1] {
dp[i] = append(dp[i], "("+left+")"+right)
}
}
}
}
return dp[n]
}
// 递归版本
func generateParenthesisDFS(n int) []string {
var res []string
var leftCnt, rightCnt int
var tmp []byte
var dfs func(cnt int)
dfs = func(cnt int) {
if cnt == n*2 {
res = append(res, string(tmp))
return
}
if leftCnt < n {
leftCnt++
tmp = append(tmp, '(')
dfs(cnt + 1)
leftCnt--
tmp = tmp[:len(tmp)-1]
}
if leftCnt > rightCnt {
rightCnt++
tmp = append(tmp, ')')
dfs(cnt + 1)
rightCnt--
tmp = tmp[:len(tmp)-1]
}
}
dfs(0)
return res
}
86
剑指 Offer II 086. 分割回文子字符串
将字符串分割成回文串, 返回所有可能的方案
#include <functional>
#include <string>
#include <vector>
using namespace std;
vector<vector<string>> partition(string s) {
int n = s.size();
vector<vector<bool>> f(n, vector<bool>(n, true));
for (int i = n - 1; i >= 0; --i) {
for (int j = i + 1; j < n; ++j) {
f[i][j] = (s[i] == s[j]) && f[i + 1][j - 1];
}
}
vector<vector<string>> ans;
vector<string> splits;
function<void(int)> dfs = [&](int i) {
if (i == n) {
ans.push_back(splits);
return;
}
for (int j = i; j < n; ++j) {
if (f[i][j]) {
splits.push_back(s.substr(i, j - i + 1));
dfs(j + 1);
splits.pop_back();
}
};
};
dfs(0);
return ans;
}
package main
func palindrome_partition(s string) (ans [][]string) {
n := len(s)
f := make([][]bool, n)
for i := range f {
f[i] = make([]bool, n)
for j := range f[i] {
f[i][j] = true
}
}
for i := n - 1; i >= 0; i-- {
for j := i + 1; j < n; j++ {
f[i][j] = s[i] == s[j] && f[i+1][j-1]
}
}
splits := []string{}
var dfs func(int)
dfs = func(i int) {
if i == n {
ans = append(ans, append([]string(nil), splits...))
return
}
for j := i; j < n; j++ {
if f[i][j] {
splits = append(splits, s[i:j+1])
dfs(j + 1)
splits = splits[:len(splits)-1]
}
}
}
dfs(0)
return
}
87
剑指 Offer II 087. 复原 IP
将字符串格式化成所有合法的ipv4地址
#include <string>
#include <vector>
using namespace std;
const int SEG_COUNT = 4;
vector<string> ans;
vector<int> segments(SEG_COUNT);
void dfs(const string &s, int segId, int segStart) {
// If we have found 4 segments and traversed the entire string, it's a valid
// IP address
if (segId == SEG_COUNT) {
if (segStart == s.size()) {
string ipAddr;
for (int i = 0; i < SEG_COUNT; ++i) {
ipAddr += to_string(segments[i]);
if (i != SEG_COUNT - 1) {
ipAddr += ".";
}
}
ans.push_back(ipAddr);
}
return;
}
// If we haven't found 4 segments but traversed the entire string, backtrack
if (segStart == s.size()) {
return;
}
// If the current number is 0, this segment can only be 0
if (s[segStart] == '0') {
segments[segId] = 0;
dfs(s, segId + 1, segStart + 1);
return;
}
// General case: enumerate each possibility and recurse
int addr = 0;
for (int segEnd = segStart; segEnd < s.size(); ++segEnd) {
addr = addr * 10 + (s[segEnd] - '0');
if (addr > 0 && addr <= 255) {
segments[segId] = addr;
dfs(s, segId + 1, segEnd + 1);
} else {
break;
}
}
}
vector<string> restoreIpAddresses(string s) {
ans.clear();
segments = vector<int>(SEG_COUNT);
dfs(s, 0, 0);
return ans;
}
package main
import "strconv"
const SEG_COUNT = 4
var (
ans []string
segments []int
)
func restoreIpAddresses(s string) []string {
segments = make([]int, SEG_COUNT)
ans = []string{}
dfs(s, 0, 0)
return ans
}
func dfs(s string, segId, segStart int) {
// 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案
if segId == SEG_COUNT {
if segStart == len(s) {
ipAddr := ""
for i := 0; i < SEG_COUNT; i++ {
ipAddr += strconv.Itoa(segments[i])
if i != SEG_COUNT-1 {
ipAddr += "."
}
}
ans = append(ans, ipAddr)
}
return
}
// 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么提前回溯
if segStart == len(s) {
return
}
// 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0
if s[segStart] == '0' {
segments[segId] = 0
dfs(s, segId+1, segStart+1)
}
// 一般情况,枚举每一种可能性并递归
addr := 0
for segEnd := segStart; segEnd < len(s); segEnd++ {
addr = addr*10 + int(s[segEnd]-'0')
if addr > 0 && addr <= 0xFF {
segments[segId] = addr
dfs(s, segId+1, segEnd+1)
} else {
break
}
}
}
88
剑指 Offer II 088. 爬楼梯的最少成本
给定一个一维数组表示爬楼梯需要的成本,并且每次支付一次就可以往上爬一个阶梯或者爬两个阶梯, 计算爬楼梯的最少成本。
#include <algorithm>
#include <vector>
using namespace std;
int minCostClimbingStairs(vector<int> &cost) {
int n = cost.size();
int pre = 0, cur = 0;
for (int i = 2; i <= n; ++i) {
int newCur = min(cur + cost[i - 1], pre + cost[i - 2]);
pre = cur;
cur = newCur;
}
return cur;
}
package main
func minCostClimbingStairs(cost []int) int {
n := len(cost)
pre, cur := 0, 0
for i := 2; i <= n; i++ {
pre, cur = cur, min(cur+cost[i-1], pre+cost[i-2])
}
return cur
}
89
剑指 Offer II 089. 房屋偷盗
给定一个一维数组表示每间房子的价值,不能偷相邻的两家,问能够偷窃到的最高金额
#include <algorithm>
#include <vector>
using namespace std;
int rob(vector<int> &nums) {
if (nums.empty()) {
return 0;
}
if (nums.size() == 1) {
return nums[0];
}
vector<int> dp(nums.size());
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for (int i = 2; i < nums.size(); ++i) {
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp.back();
}
int max(int x, int y) { return (x > y) ? x : y; }
package main
func rob(nums []int) int {
if len(nums) == 0 {
return 0
}
if len(nums) == 1 {
return nums[0]
}
dp := make([]int, len(nums))
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i := 2; i < len(nums); i++ {
dp[i] = max(dp[i-2]+nums[i], dp[i-1])
}
return dp[len(nums)-1]
}
90
剑指 Offer II 090. 环形房屋偷盗
与上一题类似,不过这一题房屋是首位相连的,问能够偷窃到的最大价值
#include <algorithm>
#include <vector>
using namespace std;
int _rob(const vector<int> &nums) {
int first = nums[0], second = max(nums[0], nums[1]);
for (int i = 2; i < nums.size(); ++i) {
int v = nums[i];
int newSecond = max(first + v, second);
first = second;
second = newSecond;
}
return second;
}
int rob(vector<int> &nums) {
int n = nums.size();
if (n == 1) {
return nums[0];
}
if (n == 2) {
return max(nums[0], nums[1]);
}
vector<int> nums1(nums.begin(), nums.end() - 1);
vector<int> nums2(nums.begin() + 1, nums.end());
return max(_rob(nums1), _rob(nums2));
}
int max(int a, int b) { return (a > b) ? a : b; }
package main
func _rob(nums []int) int {
first, second := nums[0], max(nums[0], nums[1])
for _, v := range nums[2:] {
first, second = second, max(first+v, second)
}
return second
}
func robCircle(nums []int) int {
n := len(nums)
if n == 1 {
return nums[0]
}
if n == 2 {
return max(nums[0], nums[1])
}
return max(_rob(nums[:n-1]), _rob(nums[1:]))
}
91
剑指 Offer II 091. 粉刷房子
一排房子n个,可以涂成红色、蓝色、绿色这三种颜色中的一种,需要让各个颜色并不相同
给定一个花费数组,其中的每个元素就是该房子涂成红、蓝、绿三种颜色所需要的花费
计算粉刷完所有房子最少的花费成本
#include <algorithm>
#include <iostream>
#include <vector>
int minCost(std::vector<std::vector<int>> &costs) {
std::vector<int> dp = costs[0];
for (size_t i = 1; i < costs.size(); ++i) {
std::vector<int> dpNew(3);
for (int j = 0; j < 3; ++j) {
dpNew[j] = std::min(dp[(j + 1) % 3], dp[(j + 2) % 3]) + costs[i][j];
}
dp = dpNew;
}
return std::min({dp[0], dp[1], dp[2]});
}
int main() {
std::vector<std::vector<int>> costs = {{17, 2, 17}, {16, 16, 5}, {14, 3, 19}};
std::cout << "Minimum cost: " << minCost(costs) << std::endl;
return 0;
}
package main
func minCost(costs [][]int) int {
dp := costs[0]
for _, cost := range costs[1:] {
dpNew := make([]int, 3)
for j, c := range cost {
dpNew[j] = min(dp[(j+1)%3], dp[(j+2)%3]) + c
}
dp = dpNew
}
return min(min(dp[0], dp[1]), dp[2])
}
func min(a, b int) int {
if a > b {
return b
}
return a
}
92
剑指 Offer II 092. 翻转字符
一个0 1
组成的字符串,求使得s单调递增(前面全是0 后面全是1)的最小翻转次数
#include <algorithm>
#include <iostream>
#include <string>
int min(int a, int b) { return (a < b) ? a : b; }
int minFlipsMonoIncr(const std::string &s) {
int dp0 = 0, dp1 = 0;
for (char c : s) {
int dp0New = dp0, dp1New = std::min(dp0, dp1);
if (c == '1') {
dp0New++;
} else {
dp1New++;
}
dp0 = dp0New;
dp1 = dp1New;
}
return std::min(dp0, dp1);
}
int main() {
std::string s = "00110";
std::cout << "Minimum flips: " << minFlipsMonoIncr(s) << std::endl;
return 0;
}
package main
func minFlipsMonoIncr(s string) int {
dp0, dp1 := 0, 0
for _, c := range s {
dp0New, dp1New := dp0, min(dp0, dp1)
if c == '1' {
dp0New++
} else {
dp1New++
}
dp0, dp1 = dp0New, dp1New
}
return min(dp0, dp1)
}
93
剑指 Offer II 093. 最长斐波那契数列
给定一个递增的正整数数组,找到其中最长的一个满足斐波那契数列的元素
#include <algorithm>
#include <iostream>
#include <unordered_map>
#include <vector>
int max(int a, int b) { return (a > b) ? a : b; }
int lenLongestFibSubseq(const std::vector<int> &arr) {
int n = arr.size();
std::unordered_map<int, int> indices;
for (int i = 0; i < n; ++i) {
indices[arr[i]] = i;
}
std::vector<std::vector<int>> dp(n, std::vector<int>(n, 0));
int ans = 0;
for (int i = 0; i < n; ++i) {
for (int j = n - 1; j >= 0 && arr[j] * 2 > arr[i]; --j) {
auto it = indices.find(arr[i] - arr[j]);
if (it != indices.end()) {
int k = it->second;
dp[j][i] = max(dp[k][j] + 1, 3);
ans = max(ans, dp[j][i]);
}
}
}
return ans;
}
int main() {
std::vector<int> arr = {1, 3, 7, 11, 12, 14, 18};
std::cout << "Length of longest Fibonacci-like subsequence: "
<< lenLongestFibSubseq(arr) << std::endl;
return 0;
}
package main
func lenLongestFibSubseq(arr []int) (ans int) {
n := len(arr)
indices := make(map[int]int, n)
for i, x := range arr {
indices[x] = i
}
dp := make([][]int, n)
for i := range dp {
dp[i] = make([]int, n)
}
for i, x := range arr {
for j := n - 1; j >= 0 && arr[j]*2 > x; j-- {
if k, ok := indices[x-arr[j]]; ok {
dp[j][i] = max(dp[k][j]+1, 3)
ans = max(ans, dp[j][i])
}
}
}
return
}
func max(a, b int) int {
if b > a {
return b
}
return a
}
94
剑指 Offer II 094. 最少回文分割
给定一个字符串s,找到分割的最小次数,将s分割成字串,且每个字串都是回文串,
#include <algorithm>
#include <climits>
#include <iostream>
#include <string>
#include <vector>
int minCut(const std::string &s) {
int n = s.size();
std::vector<std::vector<bool>> g(n, std::vector<bool>(n, true));
for (int i = n - 1; i >= 0; --i) {
for (int j = i + 1; j < n; ++j) {
g[i][j] = (s[i] == s[j]) && g[i + 1][j - 1];
}
}
std::vector<int> f(n, INT_MAX);
for (int i = 0; i < n; ++i) {
if (g[0][i]) {
f[i] = 0;
} else {
for (int j = 0; j < i; ++j) {
if (g[j + 1][i]) {
f[i] = std::min(f[i], f[j] + 1);
}
}
}
}
return f[n - 1];
}
int main() {
std::string s = "aab";
std::cout << "Minimum cuts needed for a palindrome partitioning: "
<< minCut(s) << std::endl;
return 0;
}
package main
import "math"
func minCut(s string) int {
n := len(s)
g := make([][]bool, n)
for i := range g {
g[i] = make([]bool, n)
for j := range g[i] {
g[i][j] = true
}
}
for i := n - 1; i >= 0; i-- {
for j := i + 1; j < n; j++ {
g[i][j] = s[i] == s[j] && g[i+1][j-1]
}
}
f := make([]int, n)
for i := range f {
if g[0][i] {
continue
}
f[i] = math.MaxInt64
for j := 0; j < i; j++ {
if g[j+1][i] && f[j]+1 < f[i] {
f[i] = f[j] + 1
}
}
}
return f[n-1]
}
95
剑指 Offer II 095. 最长公共子序列
找到两个字符串的最长的公共子序列(即满足不改变字符的相对顺序的情况下删除某些字符)
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
int max(int a, int b) { return (a > b) ? a : b; }
int longestCommonSubsequence(const std::string &text1,
const std::string &text2) {
int m = text1.size(), n = text2.size();
std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1, 0));
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (text1[i] == text2[j]) {
dp[i + 1][j + 1] = dp[i][j] + 1;
} else {
dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j]);
}
}
}
return dp[m][n];
}
int main() {
std::string text1 = "abcde";
std::string text2 = "ace";
std::cout << "Length of longest common subsequence: "
<< longestCommonSubsequence(text1, text2) << std::endl;
return 0;
}
package main
func longestCommonSubsequence(text1, text2 string) int {
m, n := len(text1), len(text2)
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
for i, c1 := range text1 {
for j, c2 := range text2 {
if c1 == c2 {
dp[i+1][j+1] = dp[i][j] + 1
} else {
dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j])
}
}
}
return dp[m][n]
}
96
剑指 Offer II 096. 字符串交织
判断两个字符串s1 s2交织(轮流选择元素)后是否与s3相等
#include <iostream>
#include <string>
#include <vector>
bool isInterleave(const std::string &s1, const std::string &s2,
const std::string &s3) {
int n = s1.size(), m = s2.size(), t = s3.size();
if (n + m != t) {
return false;
}
std::vector<bool> f(m + 1, false);
f[0] = true;
for (int i = 0; i <= n; ++i) {
for (int j = 0; j <= m; ++j) {
int p = i + j - 1;
if (i > 0) {
f[j] = f[j] && s1[i - 1] == s3[p];
}
if (j > 0) {
f[j] = f[j] || (f[j - 1] && s2[j - 1] == s3[p]);
}
}
}
return f[m];
}
int main() {
std::string s1 = "aabcc";
std::string s2 = "dbbca";
std::string s3 = "aadbbcbcac";
std::cout << "Is interleaving: "
<< (isInterleave(s1, s2, s3) ? "true" : "false") << std::endl;
return 0;
}
package main
func isInterleave(s1 string, s2 string, s3 string) bool {
n, m, t := len(s1), len(s2), len(s3)
if (n + m) != t {
return false
}
f := make([]bool, m+1)
f[0] = true
for i := 0; i <= n; i++ {
for j := 0; j <= m; j++ {
p := i + j - 1
if i > 0 {
f[j] = f[j] && s1[i-1] == s3[p]
}
if j > 0 {
f[j] = f[j] || f[j-1] && s2[j-1] == s3[p]
}
}
}
return f[m]
}
97
剑指 Offer II 097. 子序列的数目
判断字符串s中有多少个子序列是在t中出现了
#include <iostream>
#include <string>
#include <vector>
int numDistinct(const std::string &s, const std::string &t) {
int m = s.size(), n = t.size();
if (m < n) {
return 0;
}
std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1, 0));
for (int i = 0; i <= m; ++i) {
dp[i][n] = 1;
}
for (int i = m - 1; i >= 0; --i) {
for (int j = n - 1; j >= 0; --j) {
if (s[i] == t[j]) {
dp[i][j] = dp[i + 1][j + 1] + dp[i + 1][j];
} else {
dp[i][j] = dp[i + 1][j];
}
}
}
return dp[0][0];
}
int main() {
std::string s = "rabbbit";
std::string t = "rabbit";
std::cout << "Number of distinct subsequences: " << numDistinct(s, t)
<< std::endl;
return 0;
}
package main
func numDistinct(s, t string) int {
m, n := len(s), len(t)
if m < n {
return 0
}
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
dp[i][n] = 1
}
for i := m - 1; i >= 0; i-- {
for j := n - 1; j >= 0; j-- {
if s[i] == t[j] {
dp[i][j] = dp[i+1][j+1] + dp[i+1][j]
} else {
dp[i][j] = dp[i+1][j]
}
}
}
return dp[0][0]
}
98
剑指 Offer II 098. 路径的数目
一个m*n
的网格,从左上到右下,只能向下或者向右移动一步,问总共有多少条不同的路径。
#include <iostream>
#include <vector>
int uniquePaths(int m, int n) {
std::vector<std::vector<int>> dp(m, std::vector<int>(n, 0));
for (int i = 0; i < m; ++i) {
dp[i][0] = 1;
}
for (int j = 0; j < n; ++j) {
dp[0][j] = 1;
}
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
int main() {
int m = 3, n = 7;
std::cout << "Number of unique paths: " << uniquePaths(m, n) << std::endl;
return 0;
}
package main
func uniquePaths(m, n int) int {
dp := make([][]int, m)
for i := range dp {
dp[i] = make([]int, n)
dp[i][0] = 1
}
for j := 0; j < n; j++ {
dp[0][j] = 1
}
for i := 1; i < m; i++ {
for j := 1; j < n; j++ {
dp[i][j] = dp[i-1][j] + dp[i][j-1]
}
}
return dp[m-1][n-1]
}
99
剑指 Offer II 099. 最小路径之和
一个m*n
的网格,从左上角到右下角的路径,使得路径上的数字总和为最小。
每次向下或者向右移动一步
#include <algorithm>
#include <iostream>
#include <vector>
int min(int x, int y) { return (x < y) ? x : y; }
int minPathSum(const std::vector<std::vector<int>> &grid) {
if (grid.empty() || grid[0].empty()) {
return 0;
}
int rows = grid.size(), columns = grid[0].size();
std::vector<std::vector<int>> dp(rows, std::vector<int>(columns, 0));
dp[0][0] = grid[0][0];
for (int i = 1; i < rows; ++i) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < columns; ++j) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < rows; ++i) {
for (int j = 1; j < columns; ++j) {
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[rows - 1][columns - 1];
}
int main() {
std::vector<std::vector<int>> grid = {{1, 3, 1}, {1, 5, 1}, {4, 2, 1}};
std::cout << "Minimum path sum: " << minPathSum(grid) << std::endl;
return 0;
}
package main
func minPathSum(grid [][]int) int {
if len(grid) == 0 || len(grid[0]) == 0 {
return 0
}
rows, columns := len(grid), len(grid[0])
dp := make([][]int, rows)
for i := 0; i < len(dp); i++ {
dp[i] = make([]int, columns)
}
dp[0][0] = grid[0][0]
for i := 1; i < rows; i++ {
dp[i][0] = dp[i-1][0] + grid[i][0]
}
for j := 1; j < columns; j++ {
dp[0][j] = dp[0][j-1] + grid[0][j]
}
for i := 1; i < rows; i++ {
for j := 1; j < columns; j++ {
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
}
}
return dp[rows-1][columns-1]
}
100
剑指 Offer II 100. 三角形中最小路径之和
一个三角形的矩阵,从上往下找到最小路径之和
下标移动的时候只能移动到i
以及i+1
#include <algorithm>
#include <climits>
#include <iostream>
#include <vector>
int min(int x, int y) { return (x < y) ? x : y; }
int minimumTotal(const std::vector<std::vector<int>> &triangle) {
int n = triangle.size();
std::vector<std::vector<int>> f(n, std::vector<int>(n, 0));
f[0][0] = triangle[0][0];
for (int i = 1; i < n; ++i) {
f[i][0] = f[i - 1][0] + triangle[i][0];
for (int j = 1; j < i; ++j) {
f[i][j] = min(f[i - 1][j - 1], f[i - 1][j]) + triangle[i][j];
}
f[i][i] = f[i - 1][i - 1] + triangle[i][i];
}
int ans = INT_MAX;
for (int i = 0; i < n; ++i) {
ans = min(ans, f[n - 1][i]);
}
return ans;
}
int main() {
std::vector<std::vector<int>> triangle = {
{2}, {3, 4}, {6, 5, 7}, {4, 1, 8, 3}};
std::cout << "Minimum total path sum: " << minimumTotal(triangle)
<< std::endl;
return 0;
}
package main
import "math"
func minimumTotal(triangle [][]int) int {
n := len(triangle)
f := make([][]int, n)
for i := 0; i < n; i++ {
f[i] = make([]int, n)
}
f[0][0] = triangle[0][0]
for i := 1; i < n; i++ {
f[i][0] = f[i-1][0] + triangle[i][0]
for j := 1; j < i; j++ {
f[i][j] = min(f[i-1][j-1], f[i-1][j]) + triangle[i][j]
}
f[i][i] = f[i-1][i-1] + triangle[i][i]
}
ans := math.MaxInt32
for i := 0; i < n; i++ {
ans = min(ans, f[n-1][i])
}
return ans
}
101
剑指 Offer II 101. 分割等和子集
给定一个非空的正整数数组 nums ,请判断能否将这些数字分成元素和相等的两部分。
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>
bool canPartition(std::vector<int> &nums) {
int n = nums.size();
if (n < 2) {
return false;
}
int sum = std::accumulate(nums.begin(), nums.end(), 0);
int maxNum = *std::max_element(nums.begin(), nums.end());
if (sum % 2 != 0) {
return false;
}
int target = sum / 2;
if (maxNum > target) {
return false;
}
std::vector<std::vector<bool>> dp(n, std::vector<bool>(target + 1, false));
for (int i = 0; i < n; ++i) {
dp[i][0] = true;
}
dp[0][nums[0]] = true;
for (int i = 1; i < n; ++i) {
int v = nums[i];
for (int j = 1; j <= target; ++j) {
if (j >= v) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - v];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n - 1][target];
}
int main() {
std::vector<int> nums = {1, 5, 11, 5};
std::cout << "Can partition: " << (canPartition(nums) ? "true" : "false")
<< std::endl;
return 0;
}
package main
func canPartition(nums []int) bool {
n := len(nums)
if n < 2 {
return false
}
sum, max := 0, 0
for _, v := range nums {
sum += v
if v > max {
max = v
}
}
if sum%2 != 0 {
return false
}
target := sum / 2
if max > target {
return false
}
dp := make([][]bool, n)
for i := range dp {
dp[i] = make([]bool, target+1)
}
for i := 0; i < n; i++ {
dp[i][0] = true
}
dp[0][nums[0]] = true
for i := 1; i < n; i++ {
v := nums[i]
for j := 1; j <= target; j++ {
if j >= v {
dp[i][j] = dp[i-1][j] || dp[i-1][j-v]
} else {
dp[i][j] = dp[i-1][j]
}
}
}
return dp[n-1][target]
}
102
剑指 Offer II 102. 加减的目标值
给定一个数组以及target值,提供正负符号使得数组中的和等于目标值
#include <iostream>
#include <vector>
void backtrack(const std::vector<int> &nums, int target, int index, int sum,
int &count) {
if (index == nums.size()) {
if (sum == target) {
count++;
}
return;
}
backtrack(nums, target, index + 1, sum + nums[index], count);
backtrack(nums, target, index + 1, sum - nums[index], count);
}
int findTargetSumWays(const std::vector<int> &nums, int target) {
int count = 0;
backtrack(nums, target, 0, 0, count);
return count;
}
int main() {
std::vector<int> nums = {1, 1, 1, 1, 1};
int target = 3;
std::cout << "Number of ways to reach target sum: "
<< findTargetSumWays(nums, target) << std::endl;
return 0;
}
package main
func findTargetSumWays(nums []int, target int) (count int) {
var backtrack func(int, int)
backtrack = func(index, sum int) {
if index == len(nums) {
if sum == target {
count++
}
return
}
backtrack(index+1, sum+nums[index])
backtrack(index+1, sum-nums[index])
}
backtrack(0, 0)
return
}
103
剑指 Offer II 103. 最少的硬币数目
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
每种硬币的数量是无限的。
#include <algorithm>
#include <climits>
#include <iostream>
#include <vector>
int min(int a, int b) { return (a < b) ? a : b; }
int coinChange(const std::vector<int> &nums, int target) {
std::vector<int> dp(target + 1, INT_MAX);
dp[0] = 0;
for (int num : nums) {
for (int i = 0; i <= target - num; ++i) {
if (dp[i] == INT_MAX) {
continue;
}
dp[i + num] = min(dp[i + num], dp[i] + 1);
}
}
return dp[target] == INT_MAX ? -1 : dp[target];
}
int main() {
std::vector<int> nums = {1, 2, 5};
int target = 11;
std::cout << "Minimum coins needed: " << coinChange(nums, target)
<< std::endl;
return 0;
}
package main
import "math"
func coinChange(nums []int, target int) int {
dp := make([]int, target+1)
for i := 1; i <= target; i++ {
dp[i] = math.MaxInt32
}
for _, num := range nums {
for i := 0; i <= target-num; i++ {
if dp[i] == math.MaxInt32 {
continue
}
dp[i+num] = min(dp[i+num], dp[i]+1)
}
}
if dp[target] == math.MaxInt32 {
return -1
}
return dp[target]
}
104
剑指 Offer II 104. 排列的数目
给定一个由 不同 正整数组成的数组 nums ,和一个目标整数 target 。请从 nums 中找出并返回总和为 target 的元素组合的个数。数组中的数字可以在一次排列中出现任意次,但是顺序不同的序列被视作不同的组合。
#include <iostream>
#include <vector>
int combinationSum4(const std::vector<int> &nums, int target) {
std::vector<int> dp(target + 1, 0);
dp[0] = 1;
for (int i = 1; i <= target; ++i) {
for (int num : nums) {
if (num <= i) {
dp[i] += dp[i - num];
}
}
}
return dp[target];
}
int main() {
std::vector<int> nums = {1, 2, 3};
int target = 4;
std::cout << "Number of combinations: " << combinationSum4(nums, target)
<< std::endl;
return 0;
}
package main
func combinationSum4(nums []int, target int) int {
dp := make([]int, target+1)
dp[0] = 1
for i := 1; i <= target; i++ {
for _, num := range nums {
if num <= i {
dp[i] += dp[i-num]
}
}
}
return dp[target]
}
105
剑指 Offer II 105. 岛屿的最大面积
01二维矩阵,全1代表陆地,找到岛屿的最大面积
#include <iostream>
#include <vector>
int getArea(std::vector<std::vector<int>> &grid,
std::vector<std::vector<bool>> &visited, int i, int j) {
int m = grid.size(), n = grid[0].size();
if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == 0 || visited[i][j]) {
return 0;
}
visited[i][j] = true;
return getArea(grid, visited, i - 1, j) + getArea(grid, visited, i + 1, j) +
getArea(grid, visited, i, j - 1) + getArea(grid, visited, i, j + 1) +
1;
}
int maxAreaOfIsland(std::vector<std::vector<int>> &grid) {
int m = grid.size(), n = grid[0].size();
std::vector<std::vector<bool>> visited(m, std::vector<bool>(n, false));
int ans = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
int area = getArea(grid, visited, i, j);
if (area > ans) {
ans = area;
}
}
}
return ans;
}
int main() {
std::vector<std::vector<int>> grid = {
{0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0},
{0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0}};
std::cout << "Maximum area of island: " << maxAreaOfIsland(grid) << std::endl;
return 0;
}
package main
func maxAreaOfIsland(grid [][]int) int {
m, n := len(grid), len(grid[0])
visited := make([][]bool, m)
for i := 0; i < m; i++ {
visited[i] = make([]bool, n)
}
var getArea func(int, int) int
getArea = func(i, j int) int {
if i < 0 || i == m || j < 0 || j == n {
return 0
}
if grid[i][j] == 0 || visited[i][j] {
return 0
}
visited[i][j] = true
return getArea(i-1, j) + getArea(i+1, j) + getArea(i, j-1) + getArea(i, j+1) + 1
}
ans := 0
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
area := getArea(i, j)
if area > ans {
ans = area
}
}
}
return ans
}
106
剑指 Offer II 106. 二分图
二分图 定义:如果能将一个图的节点集合分割成两个独立的子集 A 和 B ,并使图中的每一条边的两个节点一个来自 A 集合,一个来自 B 集合,就将这个图称为 二分图 。
如果图是二分图,返回 true ;否则,返回 false 。
#include <functional>
#include <iostream>
#include <vector>
const int UNCOLOR = 0, RED = 1, GREEN = 2;
bool isBipartite(const std::vector<std::vector<int>> &graph) {
int n = graph.size();
std::vector<int> state(n, UNCOLOR);
bool valid = true;
std::function<void(int, int)> dfs = [&](int idx, int color) -> void {
int necolor = (color == RED) ? GREEN : RED;
state[idx] = color;
for (int ne : graph[idx]) {
if (state[ne] == UNCOLOR) {
dfs(ne, necolor);
} else {
valid = (necolor == state[ne]);
}
if (!valid) {
return;
}
}
};
for (int i = 0; i < n && valid; ++i) {
if (state[i] == UNCOLOR) {
dfs(i, RED);
}
}
return valid;
}
int main() {
std::vector<std::vector<int>> graph = {{1, 3}, {0, 2}, {1, 3}, {0, 2}};
std::cout << "Is bipartite: " << (isBipartite(graph) ? "true" : "false")
<< std::endl;
return 0;
}
package main
var (
UNCOLOR, RED, GREEN = 0, 1, 2
)
func isBipartite(graph [][]int) bool {
n := len(graph)
state := make([]int, n)
valid := true
var dfs func(idx, color int)
dfs = func(idx, color int) {
necolor := RED
if color == RED {
necolor = GREEN
}
state[idx] = color
for _, ne := range graph[idx] {
if state[ne] == UNCOLOR {
dfs(ne, necolor)
} else {
valid = necolor == state[ne]
}
if !valid {
return
}
}
}
for i := 0; i < n && valid; i++ {
if state[i] == UNCOLOR {
dfs(i, RED)
}
}
return valid
}
107
剑指 Offer II 107. 矩阵中的距离
一个01矩阵,找到每个元素距离其最近0的距离长度,结果用二维矩阵表达
#include <iostream>
#include <queue>
#include <vector>
std::vector<std::vector<int>> updateMatrix(std::vector<std::vector<int>> &mat) {
int m = mat.size();
int n = mat[0].size();
std::vector<std::vector<int>> res(m, std::vector<int>(n, 0));
std::vector<int> dx = {1, 0, -1, 0};
std::vector<int> dy = {0, -1, 0, 1};
std::queue<std::pair<int, int>> queue;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (mat[i][j] == 0) {
queue.push({i, j});
}
}
}
while (!queue.empty()) {
int x = queue.front().first;
int y = queue.front().second;
queue.pop();
for (int k = 0; k < 4; ++k) {
int nx = x + dx[k];
int ny = y + dy[k];
if (nx >= 0 && ny >= 0 && nx < m && ny < n && mat[nx][ny] == 1) {
if (res[nx][ny] == 0 || res[x][y] + 1 < res[nx][ny]) {
res[nx][ny] = res[x][y] + 1;
queue.push({nx, ny});
}
}
}
}
return res;
}
int main() {
std::vector<std::vector<int>> mat = {{0, 0, 0}, {0, 1, 0}, {1, 1, 1}};
std::vector<std::vector<int>> result = updateMatrix(mat);
for (const auto &row : result) {
for (int val : row) {
std::cout << val << " ";
}
std::cout << std::endl;
}
return 0;
}
package main
func updateMatrix(mat [][]int) [][]int {
m := len(mat)
n := len(mat[0])
res := make([][]int, m)
for i := range res {
res[i] = make([]int, n)
}
dx := []int{1, 0, -1, 0}
dy := []int{0, -1, 0, 1}
var queue [][]int
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if mat[i][j] == 0 {
queue = append(queue, []int{i, j})
}
}
}
for len(queue) > 0 {
x := queue[0][0]
y := queue[0][1]
queue = queue[1:]
for k := 0; k < 4; k++ {
nx := x + dx[k]
ny := y + dy[k]
if nx >= 0 && ny >= 0 && nx < m && ny < n && mat[nx][ny] == 1 {
if res[nx][ny] == 0 || res[x][y]+1 < res[nx][ny] {
res[nx][ny] = res[x][y] + 1
queue = append(queue, []int{nx, ny})
}
}
}
}
return res
}
108
剑指 Offer II 108. 单词演变
将单词从begin转换成end需要的最小步骤,并且每次的转换中间节点要在wordList中出现
#include <climits>
#include <iostream>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>
int ladderLength(const std::string &beginWord, const std::string &endWord,
std::vector<std::string> &wordList) {
std::unordered_map<std::string, int> wordId;
std::vector<std::vector<int>> graph;
int idCounter = 0;
auto addWord = [&](std::string word) -> int {
if (wordId.find(word) == wordId.end()) {
wordId[word] = idCounter++;
graph.push_back({});
}
return wordId[word];
};
auto addEdge = [&](std::string word) -> int {
int id1 = addWord(word);
std::string s = word;
for (size_t i = 0; i < s.size(); ++i) {
char originalChar = s[i];
s[i] = '*';
int id2 = addWord(s);
graph[id1].push_back(id2);
graph[id2].push_back(id1);
s[i] = originalChar;
}
return id1;
};
for (const auto &word : wordList) {
addEdge(word);
}
int beginId = addEdge(beginWord);
if (wordId.find(endWord) == wordId.end()) {
return 0;
}
int endId = wordId[endWord];
const int inf = INT_MAX;
std::vector<int> dist(wordId.size(), inf);
dist[beginId] = 0;
std::queue<int> queue;
queue.push(beginId);
while (!queue.empty()) {
int v = queue.front();
queue.pop();
if (v == endId) {
return dist[endId] / 2 + 1;
}
for (int w : graph[v]) {
if (dist[w] == inf) {
dist[w] = dist[v] + 1;
queue.push(w);
}
}
}
return 0;
}
int main() {
std::vector<std::string> wordList = {"hot", "dot", "dog",
"lot", "log", "cog"};
std::string beginWord = "hit";
std::string endWord = "cog";
std::cout << "Ladder length: " << ladderLength(beginWord, endWord, wordList)
<< std::endl;
return 0;
}
package main
import "math"
func ladderLength(beginWord string, endWord string, wordList []string) int {
wordId := map[string]int{}
graph := [][]int{}
addWord := func(word string) int {
id, has := wordId[word]
if !has {
id = len(wordId)
wordId[word] = id
graph = append(graph, []int{})
}
return id
}
addEdge := func(word string) int {
id1 := addWord(word)
s := []byte(word)
for i, b := range s {
s[i] = '*'
id2 := addWord(string(s))
graph[id1] = append(graph[id1], id2)
graph[id2] = append(graph[id2], id1)
s[i] = b
}
return id1
}
for _, word := range wordList {
addEdge(word)
}
beginId := addEdge(beginWord)
endId, has := wordId[endWord]
if !has {
return 0
}
const inf int = math.MaxInt64
dist := make([]int, len(wordId))
for i := range dist {
dist[i] = inf
}
dist[beginId] = 0
queue := []int{beginId}
for len(queue) > 0 {
v := queue[0]
queue = queue[1:]
if v == endId {
return dist[endId]/2 + 1
}
for _, w := range graph[v] {
if dist[w] == inf {
dist[w] = dist[v] + 1
queue = append(queue, w)
}
}
}
return 0
}
109
剑指 Offer II 109. 开密码锁
四个环形波轮组成的锁,找到转到目标值target的最小转动次数,另外不能转动到deadends列表中的数据
#include <iostream>
#include <queue>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
int openLock(std::vector<std::string> &deadends, std::string target) {
if (target == "0000") {
return 0;
}
std::unordered_set<std::string> deadendsSet(deadends.begin(), deadends.end());
if (deadendsSet.count(target) || deadendsSet.count("0000")) {
return -1;
}
std::unordered_set<std::string> visit;
auto getnei = [](std::string state) -> std::vector<std::string> {
std::vector<std::string> res;
std::string s = state;
for (int i = 0; i < 4; ++i) {
char ch = s[i];
s[i] = ch + 1;
if (s[i] > '9') {
s[i] = '0';
}
res.push_back(s);
s[i] = ch - 1;
if (s[i] < '0') {
s[i] = '9';
}
res.push_back(s);
s[i] = ch;
}
return res;
};
std::queue<std::string> queue;
queue.push("0000");
int step = 0;
while (!queue.empty()) {
int size = queue.size();
for (int i = 0; i < size; ++i) {
std::string state = queue.front();
queue.pop();
for (const auto &ne : getnei(state)) {
if (!visit.count(ne) && !deadendsSet.count(ne)) {
if (ne == target) {
return step + 1;
}
visit.insert(ne);
queue.push(ne);
}
}
}
++step;
}
return -1;
}
int main() {
std::vector<std::string> deadends = {"0201", "0101", "0102", "1212", "2002"};
std::string target = "0202";
std::cout << "Minimum turns to open lock: " << openLock(deadends, target)
<< std::endl;
return 0;
}
package main
func openLock(deadends []string, target string) int {
if target == "0000" {
return 0
}
deadendsSet := map[string]bool{}
for _, item := range deadends {
deadendsSet[item] = true
}
if deadendsSet[target] || deadendsSet["0000"] {
return -1
}
visit := map[string]bool{}
getnei := func(state string) []string {
res := []string{}
s := []byte(state)
for i, ch := range s {
s[i] = ch + 1
if s[i] > '9' {
s[i] = '0'
}
res = append(res, string(s))
s[i] = ch - 1
if s[i] < '0' {
s[i] = '9'
}
res = append(res, string(s))
s[i] = ch
}
return res
}
queue := []string{"0000"}
step := 0
for len(queue) > 0 {
newqueue := []string{}
for _, state := range queue {
for _, ne := range getnei(state) {
if !visit[ne] && !deadendsSet[ne] {
if ne == target {
return step + 1
}
visit[ne] = true
newqueue = append(newqueue, ne)
}
}
}
queue = newqueue
step++
}
return -1
}
110
剑指 Offer II 110. 所有路径
给定一个邻接矩阵结构,用于表示一个有向有环图,找到其中的所有的从0到n-1节点的路径
#include <iostream>
#include <vector>
void dfs(const std::vector<std::vector<int>> &graph, std::vector<int> &stk,
std::vector<std::vector<int>> &ans, int x) {
if (x == graph.size() - 1) {
ans.push_back(stk);
return;
}
for (int y : graph[x]) {
stk.push_back(y);
dfs(graph, stk, ans, y);
stk.pop_back();
}
}
std::vector<std::vector<int>>
allPathsSourceTarget(const std::vector<std::vector<int>> &graph) {
std::vector<std::vector<int>> ans;
std::vector<int> stk = {0};
dfs(graph, stk, ans, 0);
return ans;
}
int main() {
std::vector<std::vector<int>> graph = {{1, 2}, {3}, {3}, {}};
std::vector<std::vector<int>> result = allPathsSourceTarget(graph);
for (const auto &path : result) {
for (int node : path) {
std::cout << node << " ";
}
std::cout << std::endl;
}
return 0;
}
package main
func allPathsSourceTarget(graph [][]int) (ans [][]int) {
stk := []int{0}
var dfs func(int)
dfs = func(x int) {
if x == len(graph)-1 {
ans = append(ans, append([]int{}, stk...))
return
}
for _, y := range graph[x] {
stk = append(stk, y)
dfs(y)
stk = stk[:len(stk)-1]
}
}
dfs(0)
return
}
111
剑指 Offer II 111. 计算除法
给定一些已知变量除法获取到的值,提供数据查询结果
#include <iostream>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>
struct Edge {
int to;
double weight;
};
std::vector<double>
calcEquation(const std::vector<std::vector<std::string>> &equations,
const std::vector<double> &values,
const std::vector<std::vector<std::string>> &queries) {
std::unordered_map<std::string, int> id;
int idCounter = 0;
for (const auto &eq : equations) {
const std::string &a = eq[0];
const std::string &b = eq[1];
if (id.find(a) == id.end()) {
id[a] = idCounter++;
}
if (id.find(b) == id.end()) {
id[b] = idCounter++;
}
}
std::vector<std::vector<Edge>> graph(idCounter);
for (size_t i = 0; i < equations.size(); ++i) {
int v = id[equations[i][0]];
int w = id[equations[i][1]];
graph[v].push_back({w, values[i]});
graph[w].push_back({v, 1.0 / values[i]});
}
auto bfs = [&](int start, int end) -> double {
std::vector<double> ratios(idCounter, 0.0);
ratios[start] = 1.0;
std::queue<int> queue;
queue.push(start);
while (!queue.empty()) {
int v = queue.front();
queue.pop();
if (v == end) {
return ratios[v];
}
for (const auto &e : graph[v]) {
if (ratios[e.to] == 0.0) {
ratios[e.to] = ratios[v] * e.weight;
queue.push(e.to);
}
}
}
return -1.0;
};
std::vector<double> ans(queries.size());
for (size_t i = 0; i < queries.size(); ++i) {
const std::string &startStr = queries[i][0];
const std::string &endStr = queries[i][1];
if (id.find(startStr) == id.end() || id.find(endStr) == id.end()) {
ans[i] = -1.0;
} else {
ans[i] = bfs(id[startStr], id[endStr]);
}
}
return ans;
}
int main() {
std::vector<std::vector<std::string>> equations = {{"a", "b"}, {"b", "c"}};
std::vector<double> values = {2.0, 3.0};
std::vector<std::vector<std::string>> queries = {
{"a", "c"}, {"b", "a"}, {"a", "e"}, {"a", "a"}, {"x", "x"}};
std::vector<double> result = calcEquation(equations, values, queries);
for (double val : result) {
std::cout << val << " ";
}
std::cout << std::endl;
return 0;
}
package main
func calcEquation(equations [][]string, values []float64, queries [][]string) []float64 {
// 给方程组中的每个变量编号
id := map[string]int{}
for _, eq := range equations {
a, b := eq[0], eq[1]
if _, has := id[a]; !has {
id[a] = len(id)
}
if _, has := id[b]; !has {
id[b] = len(id)
}
}
// 建图
type edge struct {
to int
weight float64
}
graph := make([][]edge, len(id))
for i, eq := range equations {
v, w := id[eq[0]], id[eq[1]]
graph[v] = append(graph[v], edge{w, values[i]})
graph[w] = append(graph[w], edge{v, 1 / values[i]})
}
bfs := func(start, end int) float64 {
ratios := make([]float64, len(graph))
ratios[start] = 1
queue := []int{start}
for len(queue) > 0 {
v := queue[0]
queue = queue[1:]
if v == end {
return ratios[v]
}
for _, e := range graph[v] {
if w := e.to; ratios[w] == 0 {
ratios[w] = ratios[v] * e.weight
queue = append(queue, w)
}
}
}
return -1
}
ans := make([]float64, len(queries))
for i, q := range queries {
start, hasS := id[q[0]]
end, hasE := id[q[1]]
if !hasS || !hasE {
ans[i] = -1
} else {
ans[i] = bfs(start, end)
}
}
return ans
}
112
剑指 Offer II 112. 最长递增路径
给定一个 m x n 整数矩阵 matrix ,找出其中 最长递增路径 的长度。
#include <iostream>
#include <queue>
#include <vector>
std::vector<std::pair<int, int>> dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int rows, columns;
int longestIncreasingPath(std::vector<std::vector<int>> &matrix) {
if (matrix.empty() || matrix[0].empty()) {
return 0;
}
rows = matrix.size();
columns = matrix[0].size();
std::vector<std::vector<int>> outdegrees(rows, std::vector<int>(columns, 0));
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < columns; ++j) {
for (const auto &dir : dirs) {
int newRow = i + dir.first;
int newColumn = j + dir.second;
if (newRow >= 0 && newRow < rows && newColumn >= 0 &&
newColumn < columns && matrix[newRow][newColumn] > matrix[i][j]) {
outdegrees[i][j]++;
}
}
}
}
std::queue<std::pair<int, int>> queue;
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < columns; ++j) {
if (outdegrees[i][j] == 0) {
queue.push({i, j});
}
}
}
int ans = 0;
while (!queue.empty()) {
ans++;
int size = queue.size();
for (int i = 0; i < size; ++i) {
auto cell = queue.front();
queue.pop();
int row = cell.first;
int column = cell.second;
for (const auto &dir : dirs) {
int newRow = row + dir.first;
int newColumn = column + dir.second;
if (newRow >= 0 && newRow < rows && newColumn >= 0 &&
newColumn < columns &&
matrix[newRow][newColumn] < matrix[row][column]) {
outdegrees[newRow][newColumn]--;
if (outdegrees[newRow][newColumn] == 0) {
queue.push({newRow, newColumn});
}
}
}
}
}
return ans;
}
int main() {
std::vector<std::vector<int>> matrix = {{9, 9, 4}, {6, 6, 8}, {2, 1, 1}};
std::cout << "Longest increasing path: " << longestIncreasingPath(matrix)
<< std::endl;
return 0;
}
package main
var (
dirs = [][2]int{
{-1, 0},
{1, 0},
{0, -1},
{0, 1},
}
rows, columns int
)
func longestIncreasingPath(matrix [][]int) int {
if len(matrix) == 0 || len(matrix[0]) == 0 {
return 0
}
rows, columns = len(matrix), len(matrix[0])
outdegrees := make([][]int, rows)
for i := 0; i < rows; i++ {
outdegrees[i] = make([]int, columns)
}
for i := 0; i < rows; i++ {
for j := 0; j < columns; j++ {
for _, dir := range dirs {
newRow, newColumn := i+dir[0], j+dir[1]
if newRow >= 0 && newRow < rows && newColumn >= 0 && newColumn < columns && matrix[newRow][newColumn] > matrix[i][j] {
outdegrees[i][j]++
}
}
}
}
queue := [][]int{}
for i := 0; i < rows; i++ {
for j := 0; j < columns; j++ {
if outdegrees[i][j] == 0 {
queue = append(queue, []int{i, j})
}
}
}
ans := 0
for len(queue) != 0 {
ans++
size := len(queue)
for i := 0; i < size; i++ {
cell := queue[0]
queue = queue[1:]
row, column := cell[0], cell[1]
for _, dir := range dirs {
newRow, newColumn := row+dir[0], column+dir[1]
if newRow >= 0 && newRow < rows && newColumn >= 0 && newColumn < columns && matrix[newRow][newColumn] < matrix[row][column] {
outdegrees[newRow][newColumn]--
if outdegrees[newRow][newColumn] == 0 {
queue = append(queue, []int{newRow, newColumn})
}
}
}
}
}
return ans
}
113
剑指 Offer II 113. 课程顺序
总共n门课,并且有一些先后依赖顺序,返回要完成所有课程所需要的一个正确的课程顺序
#include <iostream>
#include <queue>
#include <vector>
std::vector<int> findOrder(int numCourses,
std::vector<std::vector<int>> &prerequisites) {
std::vector<std::vector<int>> edges(numCourses);
std::vector<int> indeg(numCourses, 0);
std::vector<int> result;
for (const auto &info : prerequisites) {
edges[info[1]].push_back(info[0]);
indeg[info[0]]++;
}
std::queue<int> q;
for (int i = 0; i < numCourses; ++i) {
if (indeg[i] == 0) {
q.push(i);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
result.push_back(u);
for (int v : edges[u]) {
indeg[v]--;
if (indeg[v] == 0) {
q.push(v);
}
}
}
if (result.size() != numCourses) {
return {};
}
return result;
}
int main() {
int numCourses = 4;
std::vector<std::vector<int>> prerequisites = {
{1, 0}, {2, 0}, {3, 1}, {3, 2}};
std::vector<int> order = findOrder(numCourses, prerequisites);
for (int course : order) {
std::cout << course << " ";
}
std::cout << std::endl;
return 0;
}
package main
func findOrder(numCourses int, prerequisites [][]int) []int {
var (
edges = make([][]int, numCourses)
indeg = make([]int, numCourses)
result []int
)
for _, info := range prerequisites {
edges[info[1]] = append(edges[info[1]], info[0])
indeg[info[0]]++
}
q := []int{}
for i := 0; i < numCourses; i++ {
if indeg[i] == 0 {
q = append(q, i)
}
}
for len(q) > 0 {
u := q[0]
q = q[1:]
result = append(result, u)
for _, v := range edges[u] {
indeg[v]--
if indeg[v] == 0 {
q = append(q, v)
}
}
}
if len(result) != numCourses {
return []int{}
}
return result
}
114
剑指 Offer II 114. 外星文字典
通过已知的满足特定顺序的字符串数组,来推导出这个而特定的顺序
#include <iostream>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>
std::string alienOrder(const std::vector<std::string> &words) {
std::unordered_map<char, std::vector<char>> g;
std::unordered_map<char, int> inDeg;
for (char c : words[0]) {
inDeg[c] = 0;
}
for (size_t i = 1; i < words.size(); ++i) {
const std::string &s = words[i - 1];
const std::string &t = words[i];
for (char c : t) {
inDeg[c] += 0;
}
for (size_t j = 0; j < s.size() && j < t.size(); ++j) {
if (s[j] != t[j]) {
g[s[j]].push_back(t[j]);
inDeg[t[j]]++;
break;
}
}
if (s.size() > t.size() && s.substr(0, t.size()) == t) {
return "";
}
}
std::string order;
std::queue<char> q;
for (const auto &[u, d] : inDeg) {
if (d == 0) {
q.push(u);
}
}
while (!q.empty()) {
char u = q.front();
q.pop();
order.push_back(u);
for (char v : g[u]) {
if (--inDeg[v] == 0) {
q.push(v);
}
}
}
if (order.size() != inDeg.size()) {
return "";
}
return order;
}
int main() {
std::vector<std::string> words = {"wrt", "wrf", "er", "ett", "rftt"};
std::string result = alienOrder(words);
std::cout << "Alien dictionary order: " << result << std::endl;
return 0;
}
package main
func alienOrder(words []string) string {
g := map[byte][]byte{}
inDeg := map[byte]int{}
for _, c := range words[0] {
inDeg[byte(c)] = 0
}
next:
for i := 1; i < len(words); i++ {
s, t := words[i-1], words[i]
for _, c := range t {
inDeg[byte(c)] += 0
}
for j := 0; j < len(s) && j < len(t); j++ {
if s[j] != t[j] {
g[s[j]] = append(g[s[j]], t[j])
inDeg[t[j]]++
continue next
}
}
if len(s) > len(t) {
return ""
}
}
order := make([]byte, len(inDeg))
q := order[:0]
for u, d := range inDeg {
if d == 0 {
q = append(q, u)
}
}
for len(q) > 0 {
u := q[0]
q = q[1:]
for _, v := range g[u] {
if inDeg[v]--; inDeg[v] == 0 {
q = append(q, v)
}
}
}
if cap(q) == 0 {
return string(order)
}
return ""
}
115
剑指 Offer II 115. 重建序列
https://leetcode.cn/problems/ur2n8P/?favorite=e8X3pBZi
#include <iostream>
#include <queue>
#include <vector>
bool sequenceReconstruction(const std::vector<int> &nums,
const std::vector<std::vector<int>> &sequences) {
int n = nums.size();
std::vector<std::vector<int>> g(n + 1);
std::vector<int> inDeg(n + 1, 0);
for (const auto &sequence : sequences) {
for (size_t i = 1; i < sequence.size(); ++i) {
int x = sequence[i - 1];
int y = sequence[i];
g[x].push_back(y);
inDeg[y]++;
}
}
std::queue<int> q;
for (int i = 1; i <= n; ++i) {
if (inDeg[i] == 0) {
q.push(i);
}
}
while (!q.empty()) {
if (q.size() > 1) {
return false;
}
int x = q.front();
q.pop();
for (int y : g[x]) {
if (--inDeg[y] == 0) {
q.push(y);
}
}
}
return true;
}
int main() {
std::vector<int> nums = {1, 2, 3};
std::vector<std::vector<int>> sequences = {{1, 2}, {1, 3}};
std::cout << "Can reconstruct sequence: "
<< (sequenceReconstruction(nums, sequences) ? "true" : "false")
<< std::endl;
return 0;
}
package main
func sequenceReconstruction(nums []int, sequences [][]int) bool {
n := len(nums)
g := make([][]int, n+1)
inDeg := make([]int, n+1)
for _, sequence := range sequences {
for i := 1; i < len(sequence); i++ {
x, y := sequence[i-1], sequence[i]
g[x] = append(g[x], y)
inDeg[y]++
}
}
q := []int{}
for i := 1; i <= n; i++ {
if inDeg[i] == 0 {
q = append(q, i)
}
}
for len(q) > 0 {
if len(q) > 1 {
return false
}
x := q[0]
q = q[1:]
for _, y := range g[x] {
if inDeg[y]--; inDeg[y] == 0 {
q = append(q, y)
}
}
}
return true
}
116
剑指 Offer II 116. 省份数量
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
#include <iostream>
#include <vector>
int find(std::vector<int> &parent, int x) {
if (parent[x] != x) {
parent[x] = find(parent, parent[x]);
}
return parent[x];
}
void unionSets(std::vector<int> &parent, int from, int to) {
parent[find(parent, from)] = find(parent, to);
}
int findCircleNum(const std::vector<std::vector<int>> &isConnected) {
int n = isConnected.size();
std::vector<int> parent(n);
for (int i = 0; i < n; ++i) {
parent[i] = i;
}
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (isConnected[i][j] == 1) {
unionSets(parent, i, j);
}
}
}
int ans = 0;
for (int i = 0; i < n; ++i) {
if (parent[i] == i) {
++ans;
}
}
return ans;
}
int main() {
std::vector<std::vector<int>> isConnected = {{1, 1, 0}, {1, 1, 0}, {0, 0, 1}};
std::cout << "Number of provinces: " << findCircleNum(isConnected)
<< std::endl;
return 0;
}
package main
func findCircleNum(isConnected [][]int) (ans int) {
n := len(isConnected)
parent := make([]int, n)
for i := range parent {
parent[i] = i
}
var find func(int) int
find = func(x int) int {
if parent[x] != x {
parent[x] = find(parent[x])
}
return parent[x]
}
union := func(from, to int) {
parent[find(from)] = find(to)
}
for i, row := range isConnected {
for j := i + 1; j < n; j++ {
if row[j] == 1 {
union(i, j)
}
}
}
for i, p := range parent {
if i == p {
ans++
}
}
return
}
117
剑指 Offer II 117. 相似的字符串
寻找一个字符串数组中,有多少个分组,这些分组里面都是同一种字母异位词。
#include <iostream>
#include <string>
#include <vector>
class UnionFind {
public:
UnionFind(int n) : parent(n), size(n, 1), setCount(n) {
for (int i = 0; i < n; ++i) {
parent[i] = i;
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
void unionSets(int x, int y) {
int fx = find(x), fy = find(y);
if (fx == fy)
return;
if (size[fx] < size[fy])
std::swap(fx, fy);
size[fx] += size[fy];
parent[fy] = fx;
--setCount;
}
bool inSameSet(int x, int y) { return find(x) == find(y); }
int getSetCount() const { return setCount; }
private:
std::vector<int> parent;
std::vector<int> size;
int setCount;
};
bool isSimilar(const std::string &s, const std::string &t) {
int diff = 0;
for (size_t i = 0; i < s.size(); ++i) {
if (s[i] != t[i]) {
++diff;
if (diff > 2) {
return false;
}
}
}
return true;
}
int numSimilarGroups(const std::vector<std::string> &strs) {
int n = strs.size();
UnionFind uf(n);
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (!uf.inSameSet(i, j) && isSimilar(strs[i], strs[j])) {
uf.unionSets(i, j);
}
}
}
return uf.getSetCount();
}
int main() {
std::vector<std::string> strs = {"tars", "rats", "arts", "star"};
std::cout << "Number of similar groups: " << numSimilarGroups(strs)
<< std::endl;
return 0;
}
package main
type unionFind struct {
parent, size []int
setCount int // 当前连通分量数目
}
func newUnionFind(n int) *unionFind {
parent := make([]int, n)
size := make([]int, n)
for i := range parent {
parent[i] = i
size[i] = 1
}
return &unionFind{parent, size, n}
}
func (uf *unionFind) find(x int) int {
if uf.parent[x] != x {
uf.parent[x] = uf.find(uf.parent[x])
}
return uf.parent[x]
}
func (uf *unionFind) union(x, y int) {
fx, fy := uf.find(x), uf.find(y)
if fx == fy {
return
}
if uf.size[fx] < uf.size[fy] {
fx, fy = fy, fx
}
uf.size[fx] += uf.size[fy]
uf.parent[fy] = fx
uf.setCount--
}
func (uf *unionFind) inSameSet(x, y int) bool {
return uf.find(x) == uf.find(y)
}
func isSimilar(s, t string) bool {
diff := 0
for i := range s {
if s[i] != t[i] {
diff++
if diff > 2 {
return false
}
}
}
return true
}
func numSimilarGroups(strs []string) int {
n := len(strs)
uf := newUnionFind(n)
for i, s := range strs {
for j := i + 1; j < n; j++ {
if !uf.inSameSet(i, j) && isSimilar(s, strs[j]) {
uf.union(i, j)
}
}
}
return uf.setCount
}
118
剑指 Offer II 118. 多余的边
给定一个无向图,找到找出一条可以删除的边,使其变为树
#include <iostream>
#include <vector>
int find(std::vector<int> &parent, int x) {
if (parent[x] != x) {
parent[x] = find(parent, parent[x]);
}
return parent[x];
}
bool unionSets(std::vector<int> &parent, int from, int to) {
int x = find(parent, from);
int y = find(parent, to);
if (x == y) {
return false;
}
parent[x] = y;
return true;
}
std::vector<int>
findRedundantConnection(const std::vector<std::vector<int>> &edges) {
std::vector<int> parent(edges.size() + 1);
for (size_t i = 0; i < parent.size(); ++i) {
parent[i] = i;
}
for (const auto &edge : edges) {
if (!unionSets(parent, edge[0], edge[1])) {
return edge;
}
}
return {};
}
int main() {
std::vector<std::vector<int>> edges = {{1, 2}, {1, 3}, {2, 3}};
std::vector<int> result = findRedundantConnection(edges);
std::cout << "Redundant connection: [" << result[0] << ", " << result[1]
<< "]" << std::endl;
return 0;
}
package main
func findRedundantConnection(edges [][]int) []int {
parent := make([]int, len(edges)+1)
for i := range parent {
parent[i] = i
}
var find func(int) int
find = func(x int) int {
if parent[x] != x {
parent[x] = find(parent[x])
}
return parent[x]
}
union := func(from, to int) bool {
x, y := find(from), find(to)
if x == y {
return false
}
parent[x] = y
return true
}
for _, e := range edges {
if !union(e[0], e[1]) {
return e
}
}
return nil
}
119
剑指 Offer II 119. 最长连续序列
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
要求时间复杂度为O(n)
#include <iostream>
#include <unordered_set>
#include <vector>
int longestConsecutive(const std::vector<int> &nums) {
std::unordered_set<int> numSet(nums.begin(), nums.end());
int longestStreak = 0;
for (int num : numSet) {
if (numSet.find(num - 1) == numSet.end()) {
int currentNum = num;
int currentStreak = 1;
while (numSet.find(currentNum + 1) != numSet.end()) {
currentNum++;
currentStreak++;
}
longestStreak = std::max(longestStreak, currentStreak);
}
}
return longestStreak;
}
int main() {
std::vector<int> nums = {100, 4, 200, 1, 3, 2};
std::cout << "Longest consecutive sequence length: "
<< longestConsecutive(nums) << std::endl;
return 0;
}
package main
func longestConsecutive(nums []int) int {
numSet := map[int]bool{}
for _, num := range nums {
numSet[num] = true
}
longestStreak := 0
for num := range numSet {
if !numSet[num-1] {
currentNum := num
currentStreak := 1
for numSet[currentNum+1] {
currentNum++
currentStreak++
}
if longestStreak < currentStreak {
longestStreak = currentStreak
}
}
}
return longestStreak
}
routeAlias: algo-shortlink layout: two-cols
短链系统设计
- 功能性需求
- 指定一个url能够生成一个短链接
- 当用户点击短链接的时候会跳转到原始链接
- 用户可以定义短链接格式
- 用户可以设置链接的过期时间
- 非功能性需求
- 系统必须保证高可用,否则如果服务挂了,所有的链接跳转将会失败
- 链接跳转延迟须尽可能低
- 短链接内容不能被预测到
::right::
- 多个用户是否可以发布同一个长链接,可以,因为用户可以有自己不同的短链格式,支持多次短链生成
- 用户是否可以访问其他人的链接,可以,目前这个短链生成器是所有用户通用
ShortLink sl
sl.newlink(string url, string format, time duration) // 获取短链接,生成的短链接不能重复 如果已经映射过直接返回
sl.visit(string url) // 可以使用短链也可以使用长链访问到原始链接
layout: two-cols
短链类
@startuml
class LinkMap {
-shortlinks: set[string] 用于判断一个短链是否已经存在
-links :map[string]string 长链向短链的映射
+register(string link, string format)
+visit(string link)
}
@enduml
layout: two-cols
用户类
@startuml
class User {
-id :int 用户的id
-links :int[] 用户的链接id
+register(string link)
+visit(string link)
}
@enduml
And now, a quiz:
{{#quiz ../../public/quiz/c++/1.toml}}
- volatile static const extern等关键字
- 宏定义和展开、内联函数区别
- STL原理及实现
- 什么是虚函数
- 指针
- C++ 内存分配机制
- override和overload的区别
- 写string类的构造,析构,拷贝函数
- C++中类成员的访问权限有那些?
- C++多态的实现有那几种?他们有什么不同?
- C++中右值引用有什么作用?
- 面向对象的三大特征是什么
- 静态分配内存和动态分配内存有什么区别?
- C++结构体内存对齐
- 讲一讲C++中的原子操作有那些?
- C++中动态链接库和静态连接库的区别是什么?
- 在C++中,对一个对象先malloc后delete这样使用可以吗?有什么风险?
- 在C++中,三个全局变量相互依赖,程序应该如何初始化呢?300个呢?
- 在C++中为什么需要深拷贝,浅拷贝会存在哪些问题?
- 如何构造一个类使得只能在堆上或者栈上分配内存?
- 什么是C++的内存模型?
- 指针和引用在内存中的表现形式有何不同?
- 内存映射文件是什么?如何用它来处理大文件?
- C++中结构体内存布局的规则是什么?
- 在C++中,用堆和用栈谁更快一点?
- C++中struct和class有什么区别?
- 如果A这个对象对应的类是一个空类,那么sizeof(A)的值是多少?
- 如果A这个指针指向一个数组,那么sizeof(A)的值是多少?
- 如果A是某一个类的指针,那么在它等于nullptr的情况下能直接调用里面的A对应类里面的public函数吗?
- C++中,结构体可以直接赋值吗?
- #define和const的区别有那些?
- 在C++的map中,[]与insert有那些区别?
- 在32位和64位系统中,指针分别为多大?
- weak_ptr是如何解决shared_ptr循环引用的?
- 虚函数是否可以声明为static?
- 如何使用gdb来定位C++程序中的死锁?
- C++中常用的类优化技术有那些?
- C++的atomic代码底层是如何实现的?
- 原子变量的内存序是什么?
- 引用变量
- C++中四种cast的转换?
- 内存池是什么?在C++中如何设计一个简单的内存池?
- set,mutiset,map,mutimap之间都有什么区别?
- 在C++的算法库中,find()和binary_search()有什么区别?
- lower_bound()和upper_bound()有什么区别?
- 函数参数的入栈顺序是什么,从左到右还是从右到左?
- 讲讲函数调用的过程
- c++11/14/17/21标准详解
岗位要求描述
或许可以走游戏服务器端开发方向?正好有独立游戏开发经验,也有服务端经验,可惜只给10-15
c++11标准
C++11 标准是 C++ 语言的一个重大更新,引入了许多新的语言特性和库功能,旨在提高代码的可读性、性能和编写效率。以下是 C++11 标准中最重要的几个改进和新增特性:
1. 自动类型推导 (auto)
C++11 引入了 auto 关键字,允许编译器根据右值推导变量类型。它极大简化了复杂类型的声明,并让代码更简洁。
示例:
auto x = 42; // x 被推导为 int
auto y = 3.14; // y 被推导为 double
auto z = someFunc(); // z 的类型根据 someFunc() 的返回值推导
2. 范围 for 循环
C++11 引入了基于范围的 for 循环,使得遍历容器变得更简便。
示例:
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto v : vec) {
std::cout << v << std::endl;
}
3. Lambda 表达式
Lambda 表达式是 C++11 的一个非常重要的特性,它提供了内联定义匿名函数的能力,常用于回调、函数对象等场景。
语法:
[捕获列表](参数列表) -> 返回类型 { 函数体 }
示例:
auto add = [](int a, int b) -> int {
return a + b;
};
std::cout << add(1, 2) << std::endl;
4. 右值引用与移动语义 (&& 和 std::move)
C++11 引入了右值引用(T&&),用于实现移动语义,解决拷贝开销问题。通过右值引用和 std::move,可以避免不必要的深拷贝,提升性能。
示例:
std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = std::move(vec1); // vec1 的资源被移动到 vec2 中
移动构造函数与移动赋值运算符:
class MyClass {
public:
MyClass(MyClass&& other) { /* 实现移动构造 */ }
MyClass& operator=(MyClass&& other) { /* 实现移动赋值 */ }
};
5. 智能指针(std::unique_ptr 和 std::shared_ptr)
C++11 引入了 std::unique_ptr 和 std::shared_ptr,这是现代 C++ 中管理动态内存的推荐方式,减少了手动管理内存的风险(如内存泄漏)。
std::unique_ptr:唯一拥有某个资源,不能拷贝,但可以移动。 std::shared_ptr:多个指针共享同一资源,使用引用计数来管理资源的生命周期。 示例:
std::unique_ptr<int> p1(new int(10)); // 独占所有权
std::shared_ptr<int> p2 = std::make_shared<int>(20); // 共享所有权
6. nullptr
C++11 用 nullptr 取代了 C++98 的 NULL,使指针与整数 0 更明确地区分。
示例:
int* ptr = nullptr;
7. 静态断言 (static_assert)
静态断言允许在编译时进行条件检查,提高了代码的健壮性和可维护性。
示例:
static_assert(sizeof(int) == 4, "int 大小必须是 4 字节");
8. constexpr
constexpr 关键字允许函数或变量在编译时求值,这对常量表达式的优化有很大帮助。
示例:
constexpr int square(int x) {
return x * x;
}
int arr[square(5)]; // 编译时就能计算出 arr 的大小为 25
9. std::thread 和并发库
C++11 提供了标准线程支持,通过 std::thread 类启动线程,使用互斥量(std::mutex)、锁(std::lock_guard)等进行同步控制。
示例:
void task() {
std::cout << "Task running" << std::endl;
}
std::thread t(task);
t.join(); // 等待线程结束
10. std::tuple
C++11 引入了 std::tuple,它是一种可以存储任意数量不同类型值的容器。
示例:
std::tuple<int, double, std::string> t(1, 2.3, "C++11");
int i;
double d;
std::string s;
std::tie(i, d, s) = t; // 解包 tuple
11. std::initializer_list
C++11 引入了 std::initializer_list,支持通过大括号列表初始化容器或类的成员变量。
示例:
std::vector<int> vec = {1, 2, 3, 4}; // 初始化列表
12. 枚举类 (enum class)
C++11 引入了枚举类,它提高了类型安全性,避免了传统枚举类型的隐式转换问题。
示例:
enum class Color { Red, Green, Blue };
Color c = Color::Red;
13. decltype
decltype 关键字可以用于获取表达式的类型,常用于模板编程和自动推导复杂表达式的返回类型。
示例:
int x = 10;
decltype(x) y = 20; // y 的类型与 x 相同,即 int
14. 移动捕获(Lambda 改进)
C++11 中 Lambda 表达式可以通过 [=] 捕获外部变量,但有时需要显式地移动某些变量。为此,C++11 提供了捕获列表中的 std::move。
示例:
auto ptr = std::make_unique<int>(10);
auto lambda = [p = std::move(ptr)] { std::cout << *p << std::endl; };
c++14标准
C++14 是 C++11 标准的一个增量更新,它的目标是对 C++11 做出一些修订和小改进。C++14 并没有像 C++11 那样引入大量的新特性,但它对 C++11 的语言和库进行了优化、简化,提升了编程体验和性能。以下是 C++14 的主要改进和新增特性:
1. 泛型 Lambda 表达式改进
C++14 允许 Lambda 表达式的参数使用自动类型推导(即 auto),从而支持更广泛的泛型编程。这简化了 Lambda 表达式的使用,尤其是涉及模板的情况下。
C++11 的 Lambda:
auto lambda = [](int x, double y) {
return x + y;
};
C++14 的 Lambda:
auto lambda = [](auto x, auto y) {
return x + y; // 可以传递不同类型的参数
};
std::cout << lambda(1, 2.5) << std::endl; // 结果为 3.5
2. auto 返回类型推导
C++11 引入了 auto 关键字,但在函数返回类型上,必须显式声明返回类型,或者使用尾随返回类型。C++14 则允许编译器自动推导函数的返回类型。
示例:
auto add(int x, int y) {
return x + y; // 编译器自动推导返回类型为 int
}
在 C++11 中,如果使用 auto,需要这样写:
auto add(int x, int y) -> int {
return x + y;
}
3. 常量表达式中的更多功能 (constexpr)
C++14 对 constexpr 关键字做了进一步扩展,允许 constexpr 函数变得更灵活。C++11 中的 constexpr 函数必须是简单的一行计算,C++14 则允许在 constexpr 函数中使用更复杂的语句,如循环和条件语句。
示例:
constexpr int factorial(int n) {
int result = 1;
for (int i = 1; i <= n; ++i)
result *= i;
return result;
}
int arr[factorial(5)]; // 编译时计算 5 的阶乘,arr 的大小为 120
4. std::make_unique
在 C++11 中,智能指针 std::unique_ptr 是引入的重要特性,但当时没有类似于 std::make_shared 的创建 std::shared_ptr 的便捷工厂函数。C++14 引入了 std::make_unique,用于创建 std::unique_ptr 对象,使代码更加安全和简洁。
示例:
auto ptr = std::make_unique<int>(10); // 创建一个智能指针
在 C++11 中,你需要这样写:
std::unique_ptr<int> ptr(new int(10));
std::make_unique 优点包括:
避免手动调用 new,减少内存泄漏风险。 更加高效,因为它可以防止对象初始化和指针赋值的两步操作。
5. 二进制字面量
C++14 引入了二进制字面量,使得以二进制形式表示数值更加直观。通过前缀 0b 或 0B 来定义二进制数字。
示例:
int binary = 0b1010; // 二进制 1010 对应十进制的 10
这个功能对处理位运算、嵌入式编程或需要直接操作硬件的场景尤其有用。
6. 数字分隔符(单引号)
C++14 引入了数字分隔符,可以用单引号(')来分隔数字,使得长数字更易于阅读。
示例:
int million = 1'000'000; // 等价于 1000000
long hex = 0x1'ABC'DEF;
这种写法不会影响数字的数值,纯粹是为了提升可读性,尤其是在金融和科学计算中处理大数时非常方便。
7. 返回类型后置推导改进
在 C++11 中,函数的返回类型推导只能通过“尾随返回类型”来实现。而 C++14 引入了可以直接使用 decltype(auto),让返回类型的推导更加灵活。
示例:
int x = 10;
decltype(auto) func() {
return (x); // 返回类型是 int&
}
这种写法可以确保返回的表达式类型与实际表达式类型保持一致(包括引用)。
8. std::integer_sequence 和 std::index_sequence
这些新工具极大简化了与参数包(variadic template)相关的元编程,它们允许开发者为编译时生成的一系列整数类型提供一个索引序列。这对于一些需要展开参数包的场景非常有用。
示例:
template<std::size_t... Indices>
void print_indices(std::index_sequence<Indices...>) {
((std::cout << Indices << " "), ...);
}
int main() {
print_indices(std::make_index_sequence<5>{}); // 输出:0 1 2 3 4
}
9. [[deprecated]]
属性
C++14 引入了 [[deprecated]]
属性,用于标记不推荐使用的函数或类,编译器会在使用这些标记的元素时给出警告。
示例:
[[deprecated("使用新函数 newFunc 代替")]]
void oldFunc() {}
void newFunc() {}
这样当程序员调用 oldFunc 时,编译器会提示使用新函数 newFunc。
10. 扩展的捕获(Lambda 表达式)
C++14 Lambda 表达式的捕获列表支持直接移动捕获。C++11 只允许按值或按引用捕获外部变量,而 C++14 支持通过移动语义捕获。
示例:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
std::cout << *p << std::endl; // 打印 42
};
总结
C++14 标准在 C++11 的基础上,做了许多实用性、可读性和性能方面的改进。其主要的目标是通过对语言和库的小幅更新,改善开发者体验,使得编写高效、简洁的 C++ 代码更加容易。C++14 的特性虽然没有 C++11 那样革命性,但它解决了一些开发者在使用 C++11 时遇到的实际问题,并为进一步的标准(如 C++17 和 C++20)奠定了基础。
c++17标准
C++17 是 C++ 标准中的一个重要更新,带来了许多新的语言特性和库功能。相比 C++11 和 C++14,C++17 既包含了一些编译时和运行时的优化,也引入了更多便利开发者的功能。以下是 C++17 的主要新特性和改进:
1. 结构化绑定(Structured Bindings)
C++17 引入了结构化绑定,可以将结构、元组或数组的元素直接绑定到局部变量中。这使得从复杂类型中解包数据变得更加简洁。
示例:
std::tuple<int, double, std::string> t(1, 2.3, "C++17");
auto [x, y, z] = t; // 直接将 tuple 的元素解包到 x, y, z
struct Point { int x; int y; };
Point p { 10, 20 };
auto [a, b] = p; // 将 Point 的成员解包到 a 和 b
2. if constexpr
if constexpr 是 C++17 中对条件编译的改进,它允许在编译时根据条件选择执行代码。与普通的 if 不同,如果 if constexpr 的条件为 false,则其对应的代码块不会被编译,从而避免了无效代码的编译错误。
示例:
template<typename T>
void print(T t) {
if constexpr (std::is_integral<T>::value) {
std::cout << t << " is an integer" << std::endl;
} else {
std::cout << t << " is not an integer" << std::endl;
}
}
这里 if constexpr 允许在编译时决定哪个分支会被执行,另一个分支则不会被编译,确保编译时的安全性和性能。
3. 折叠表达式(Fold Expressions)
C++17 中的折叠表达式用于简化对参数包(variadic template)的处理。它允许使用参数包时的递归展开更直观和简洁。
示例:
template<typename... Args>
auto sum(Args... args) {
return (... + args); // 折叠表达式,计算所有参数的和
}
std::cout << sum(1, 2, 3, 4); // 输出:10
在这里,(... + args) 是一个右折叠表达式,它自动展开成 1 + 2 + 3 + 4。
4. std::optional
std::optional 是一个新的容器类型,用于表示一个值可能存在,也可能不存在。这种类型有助于避免空指针或无效状态,提供了更安全的 API。
示例:
std::optional<int> find(int value) {
if (value == 42) {
return 42; // 返回有效值
}
return std::nullopt; // 返回无效值
}
auto result = find(42);
if (result) {
std::cout << "Found: " << *result << std::endl;
} else {
std::cout << "Not found" << std::endl;
}
std::optional 可以避免使用 nullptr 或 0 表示“无效值”的习惯,增强代码的可读性和安全性。
5. std::variant
std::variant 是一种类型安全的联合体,它允许存储多种不同类型的值,但每次只能存储其中一种类型。它提供了比传统联合体(union)更安全的接口,并且支持类型检查。
示例:
std::variant<int, double, std::string> v;
v = 42;
v = "Hello";
if (std::holds_alternative<std::string>(v)) {
std::cout << "Variant holds a string: " << std::get<std::string>(v) << std::endl;
}
std::variant 非常适合需要在运行时存储多种类型但又不想牺牲类型安全的场景。
6. std::any
std::any 是 C++17 引入的一个类型安全的容器,它可以存储任意类型的值,但与 std::variant 不同,std::any 并不限定存储的类型,允许存储任何类型,并在运行时通过 std::any_cast 来提取该值。
示例:
std::any a = 1;
a = std::string("C++17");
try {
std::cout << std::any_cast<std::string>(a) << std::endl; // 输出:C++17
} catch (const std::bad_any_cast& e) {
std::cout << "Bad cast" << std::endl;
}
std::any 非常灵活,但也因此牺牲了一些类型安全,因此通常只在必须的时候使用。
7. 平行算法(Parallel Algorithms)
C++17 的标准库中增加了对并行算法的支持,许多 STL 算法现在可以通过新引入的执行策略并行执行。执行策略包括顺序(std::execution::seq)、并行(std::execution::par)、以及并行且矢量化(std::execution::par_unseq)等。
示例:
#include <execution>
#include <vector>
#include <algorithm>
std::vector<int> vec = {1, 2, 3, 4, 5};
std::for_each(std::execution::par, vec.begin(), vec.end(), [](int& n) {
n *= 2; // 并行执行
});
通过引入并行算法,C++17 提供了更高效的方式来处理大数据集和计算密集型任务。
- constexpr 的改进 C++17 对 constexpr 进行了进一步的增强,允许 constexpr 函数执行更多复杂操作,比如定义局部变量、条件语句、循环等,使得在编译时执行的计算更加灵活。
示例:
constexpr int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
static_assert(factorial(5) == 120, "Factorial calculation is wrong");
这使得编译时的常量计算功能更强大,可以更有效地优化代码。
9. 内联变量(Inline Variables)
C++17 引入了内联变量(inline variables)的概念,允许在头文件中定义全局变量或静态成员变量,而不会引发链接错误。这解决了 C++98 和 C++11 中需要在 .cpp 文件中定义全局变量的痛点。
示例:
struct MyClass {
static inline int value = 10; // 内联静态成员变量
};
内联变量可以定义在头文件中,并且可以在多个翻译单元中共享,而不必担心链接冲突。
10. 标准库容器的改进
C++17 对标准库容器进行了若干改进,包括:
std::vector 和 std::string 支持 data() 方法,用于返回底层数组的指针,这在处理与 C API 交互时尤其有用。 std::map 和 std::set 的 insert 和 emplace 函数返回插入位置的迭代器,以简化代码。 示例:
std::vector<int> v = {1, 2, 3};
int* p = v.data(); // 直接获取底层数组指针
11. 编译时静态断言消息改进
C++17 改进了静态断言(static_assert)的语法,使其可以省略错误消息。在 C++11 中,static_assert 必须提供一个错误消息,而 C++17 中可以直接省略消息。
示例:
static_assert(sizeof(int) == 4); // 不需要提供错误消息
c++20标准
C++20 是 C++ 标准中的一次重要更新,被认为是自 C++11 以来最具影响力的标准之一。它带来了大量新特性和改进,涵盖了语言、库、并发、编译时编程等多个方面。以下是 C++20 的一些关键特性及其详细解释。
1. 概念 (Concepts)
概念(Concepts)是 C++20 中的一个核心特性,用于定义模板的约束条件。它们通过提供明确的接口要求,简化了模板编程,并在编译期间捕获模板参数的错误,从而提高了代码的可读性和安全性。
示例:
template<typename T>
concept Integral = std::is_integral_v<T>;
template<Integral T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(3, 4) << std::endl; // OK
// std::cout << add(3.0, 4.0) << std::endl; // 编译错误,不满足 Integral 概念
}
概念通过约束模板参数类型,避免了模板引发的晦涩错误,提供了更清晰的编译时信息。
2. 协程 (Coroutines)
协程(Coroutines)是 C++20 中引入的一个高级语言特性,允许编写异步代码和生成器。协程通过 co_await、co_yield 和 co_return 关键字提供了暂停、恢复和异步操作的机制,使得异步编程和生成器的实现更加高效和简洁。
示例:
#include <coroutine>
#include <iostream>
struct SimpleCoroutine {
struct promise_type {
SimpleCoroutine get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
SimpleCoroutine myCoroutine() {
std::cout << "Hello from coroutine!" << std::endl;
co_return;
}
int main() {
myCoroutine();
}
协程可以在处理 I/O 密集型任务、游戏循环、并发编程等场景中大显身手,减少上下文切换的开销。
3. 范围 (Ranges)
C++20 引入了新的 Ranges 库,这是对现有 STL 算法和迭代器的一次重大改进。Ranges 提供了一种更简洁、更安全的方式来操作集合。它支持基于管道操作符的懒计算链式调用,让代码更具表达性。
示例:
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
auto result = numbers | std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; });
for (int n : result) {
std::cout << n << " "; // 输出:4 16 36
}
}
Ranges 库简化了数据流处理,减少了对迭代器的显式操作,极大提升了可读性和可维护性。
4. 模块 (Modules)
模块(Modules)是对 C++ 代码的组织方式的一次重大变革,旨在替代传统的头文件机制。模块可以减少编译时间,避免头文件包含的冗余问题,并且改善代码的封装和安全性。
示例:
// mymodule.ixx
export module mymodule;
export int add(int a, int b) {
return a + b;
}
// main.cpp
import mymodule;
int main() {
std::cout << add(1, 2) << std::endl;
}
模块通过显式的导入(import)和导出(export),避免了头文件和实现文件之间的重复,并提升了大规模项目的编译速度。
5. 三向比较 (Three-way Comparison, <=>)
C++20 引入了三向比较运算符,也叫“飞船运算符”(<=>),它可以自动生成比较函数,用于简化对象比较的实现。<=> 运算符返回一个结果,用于表示小于、等于或大于的关系。
示例:
#include <compare>
struct Point {
int x, y;
auto operator<=>(const Point&) const = default; // 自动生成比较操作
};
int main() {
Point p1{1, 2}, p2{1, 3};
std::cout << std::boolalpha << (p1 < p2) << std::endl; // 输出:true
}
三向比较运算符简化了多个比较运算符的编写,特别适用于需要排序的对象。
6. consteval 和 constinit
C++20 引入了两个新的关键字:consteval 和 constinit,用于改进编译时常量表达式的处理。
- consteval:声明一个函数为 consteval,意味着这个函数只能在编译时被调用,任何试图在运行时调用它的行为都会导致编译错误。consteval 强调了编译时求值的必要性,确保调用该函数的所有表达式在编译时都能求值,增强了类型安全性。
- constinit:当使用 constinit 声明一个变量时,编译器会确保该变量在定义时初始化,并且只允许常量表达式作为其初始值。constinit 防止了可能的未初始化常量的运行时行为,确保在使用该变量之前,它已经被初始化并且是常量的。
示例:
consteval int square(int n) {
return n * n;
}
constinit int value = square(4); // 确保在编译时初始化
这些特性增强了对常量表达式的控制,避免了潜在的运行时错误。
7. 范围 for 循环的增强
C++20 对范围 for 循环做了进一步的增强,现在可以直接解包 pair 或结构体成员。
示例:
#include <map>
#include <iostream>
int main() {
std::map<int, std::string> m = {{1, "one"}, {2, "two"}};
for (auto [key, value] : m) {
std::cout << key << ": " << value << std::endl;
}
}
这种语法糖让代码更加简洁,适用于需要处理键值对或结构体的场景。
8. std::span
std::span 是一种轻量级的非拥有类型视图,表示一段连续的内存块。它非常适合用于处理数组或容器的子集,而不需要复制数据。
示例:
#include <span>
#include <vector>
#include <iostream>
void print_span(std::span<int> s) {
for (int n : s) {
std::cout << n << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
print_span(v); // 输出:1 2 3 4 5
print_span(v.data()); // 输出:1
}
std::span 在处理数组、容器的子集以及与 C 风格数组交互时非常有用,它不涉及数据所有权,提供了安全的边界检查。
9. 范围 for 循环中的初始化
C++20 增加了在范围 for 循环中使用初始化的能力。这意味着可以在 for 循环的范围表达式之前引入一个初始化语句,从而简化代码结构。
示例:
#include <vector>
int main() {
for (std::vector<int> v = {1, 2, 3, 4}; auto n : v) {
std::cout << n << " ";
}
}
这种增强使得代码的可读性和结构性得到了提升,尤其是在需要预处理数据的情况下。
10. std::format
C++20 引入了 std::format,一个功能强大的字符串格式化库,类似于 Python 的 format 函数,取代了之前的 sprintf 和 std::ostringstream,提供了更现代的格式化机制。
示例:
#include <format>
#include <iostream>
int main() {
std::string name = "C++20";
std::cout << std::format("Hello, {}!", name) << std::endl; // 输出:Hello, C++20!
}
std::format 提供了更简洁、安全的格式化方式,避免了传统 printf 风格带来的潜在安全问题。
11. 异步任务的改进 (std::jthread)
C++20 引入了 std::jthread,一种改进版的 std::thread,它会在作用域结束时自动中止线程,避免了手动 join 的麻烦。
示例:
#include <thread>
#include <iostream>
void task() {
std::cout << "Running in jthread" << std::endl;
}
int main() {
std::jthread t(task); // jthread 会自动 join
}
std::jthread 通过简化线程管理,减少了编写并发代码时的错误风险。
总结 C++20 是对 C++ 语言标准的一个重大更新,涵盖了编译时编程、协程、模块、概念等众多领域。它不仅提升了代码的性能和表达能力,还显著提高了开发者的生产力。C++20 标准旨在让 C++ 编程更加现代、简洁和高效,适应未来的编程需求。
c++23
C++23 是 C++ 语言的一个更新版本,作为 C++20 的继任者,它为开发者带来了一系列新特性、改进和 bug 修复。它延续了现代 C++ 的演进趋势,主要聚焦于提高语言的简洁性、功能性和性能。以下是 C++23 的一些重要更新和新特性。
1. 语言特性改进
1.1 模块化 (Modules) 的完善 C++20 引入了模块化系统,但还有许多未解决的问题。C++23 对其进行了多项改进,例如更好的跨模块优化、更简洁的编译和链接过程。模块有助于减少头文件的包含依赖性,降低编译时间。
constexpr 的增强
C++23 中进一步扩展了 constexpr 的适用性,使得更多函数、容器、算法可以在编译时执行。例如:
动态分配的支持:允许 new 和 delete 在 constexpr 中使用。 std::string 和其他标准库容器可以在 constexpr 环境下使用。 这使得更多的编译时计算变得可能,进一步提高程序的性能。
Pattern Matching(模式匹配)初步实现
虽然完整的模式匹配尚未完全实现,但 C++23 引入了基础的结构化绑定和模式匹配功能。这让代码中对某些数据结构的拆解和访问更加简洁。例如,std::variant 和类似类型可以通过模式匹配进行简洁的类型匹配和处理。
if consteval
if consteval 是 C++23 引入的新语法,它允许在编译期选择执行路径。与 constexpr 类似,但针对编译时和运行时的行为区分更为明确。例如:
void foo() {
if consteval {
// 编译时执行的代码
} else {
// 运行时执行的代码
}
}
这种特性让开发者能更加细粒度地控制编译时和运行时的行为。
2. Ranges 库的扩展
C++20 引入的 Ranges 库在 C++23 中得到了进一步扩展和完善。C++23 增加了更多与 Ranges 相关的算法,改进了 view 的操作,增加了范围适配器的灵活性。例如:
新增 std::ranges::to,可以方便地将范围转换为容器。 引入了 join_with 视图,可以将多个 range 连接成一个。 lazy_split 允许延迟分割范围。 这些改进让 Ranges 库在处理复杂数据流时更加自然和简洁。
3. std::expected 类型
C++23 引入了 std::expected,用于处理可能失败的函数返回值。这是对 std::optional 和异常机制的补充,提供了另一种错误处理的模式。std::expected 可以显式表示函数成功或失败的结果,而无需抛出异常或使用 std::optional 表示缺值。
使用示例:
std::expected<int, std::string> get_value(bool succeed) {
if (succeed) {
return 42;
} else {
return std::unexpected("Error occurred");
}
}
4. std::flat_map 和 std::flat_set
C++23 引入了 std::flat_map 和 std::flat_set 作为 std::map 和 std::set 的替代品。它们采用了平坦数组来存储数据,提供了比基于树结构的容器更好的缓存局部性,从而提高了性能,特别是在频繁的查找操作中。
5. std::print
C++23 引入了 std::print 和 std::println,提供了一个简单的标准化打印函数,类似于其他语言中的 printf 或 println,避免了使用 std::cout 的复杂性。例如:
std::print("Hello, World!\n");
这简化了基础的输出操作,避免了 iostream 的冗长语法。
6. 并发和多线程支持
std::jthread 的改进
std::jthread 是 C++20 中引入的类,它简化了线程的管理,自动加入线程而无需手动调用 join。C++23 对此类进行了进一步的增强,包括对线程中断(stop token)的更好支持,使得多线程编程更加安全和高效。
std::atomic_ref
C++23 引入了 std::atomic_ref,它允许对现有对象的原子操作,而无需将对象本身声明为 std::atomic。这为处理非原子对象的线程安全操作提供了更灵活的手段。
7. constexpr 动态内存管理
C++23 允许在 constexpr 环境中动态分配内存,这极大增强了 constexpr 的能力,允许在编译时进行复杂的内存操作和管理。
8. 基于 contract 的编程(延期)
C++20 原本计划引入的 contracts 机制,在 C++23 中仍然被推迟。Contracts 允许在函数定义中指定前置条件、后置条件和不变式,提供更强的约束条件和调试支持。然而由于设计复杂性,它仍未被引入。
9. 占位符类型推导(deducing this)
C++23 引入了 deducing this 特性,允许通过占位符进行成员函数的类型推导。这简化了某些情况下成员函数模板的使用,尤其是在返回类型依赖于 this 的情况下。
C++23 与 C++20 的对比, C++23 更加强调性能和编译期能力(如 constexpr 的增强)。模式匹配和 Ranges 库的改进进一步提升了代码的表达能力。C++23 更注重标准库的扩展,如 std::expected 和 std::flat_map,提供了更好的开发者工具。
C++23 对并发的改进,使多线程编程变得更加易用和安全。总结来说,C++23 是 C++20 的自然进化版本,通过进一步增强编译期计算、标准库的功能、并发编程的支持,以及更简洁的语法,提升了开发者的编程体验和程序的性能。
访问控制符
- public:公有成员,类外部可以访问。
- protected:保护成员,只有类的成员函数、友元函数以及派生类可以访问。
- private:私有成员,只有类的成员函数和友元函数可以访问,派生类不能直接访问。
当类进行继承时,基类中的访问控制符会影响派生类对基类成员的访问权限。C++支持三种继承方式:
- public继承:基类的public成员在派生类中保持public,protected成员保持protected,private成员依然无法访问。
- protected继承:基类的public和protected成员在派生类中都变为protected,private成员依然无法访问。
- private继承:基类的public和protected成员在派生类中都变为private,private成员依然无法访问。
友元
友元函数
#include <iostream>
using namespace std;
class Person {
private:
string name;
int age;
public:
Person(string n, int a) : name(n), age(a) {}
// 声明 friend 函数
friend void showPersonInfo(const Person& p);
};
// 定义 friend 函数
void showPersonInfo(const Person& p) {
cout << "Name: " << p.name << ", Age: " << p.age << endl; // 可以访问 private 成员
}
int main() {
Person p("Alice", 30);
showPersonInfo(p);
return 0;
}
友元类
#include <iostream>
using namespace std;
class Person {
private:
string name;
int age;
protected:
int protectedVar;
public:
Person(string n, int a) : name(n), age(a), protectedVar(100) {}
// 将 FriendClass 声明为友元类
friend class FriendClass;
};
// FriendClass 可以访问 Person 的 private 和 protected 成员
class FriendClass {
public:
void displayPersonInfo(const Person& p) {
// 可以访问 private 成员
cout << "Name: " << p.name << endl;
cout << "Age: " << p.age << endl;
// 可以访问 protected 成员
cout << "Protected Var: " << p.protectedVar << endl;
}
};
int main() {
Person person("Alice", 25);
FriendClass friendClass;
// 友元类访问 Person 的私有和保护成员
friendClass.displayPersonInfo(person);
return 0;
}
迭代器接口
支持 for 循环遍历的容器,主要依赖于迭代器接口的实现。为了支持 for 循环(尤其是 C++11 引入的范围 for 循环),容器必须实现以下两个方法:
- begin():返回指向容器第一个元素的迭代器。
- end():返回指向容器最后一个元素后面的迭代器。
如果你要让自己的类支持 for 循环遍历,你需要在该类中实现 begin() 和 end() 方法,返回一个迭代器,迭代器需要实现以下几个操作:
- 解引用 (*):访问元素。
- 递增 (++):移动到下一个元素。
- 比较 (!=):检查迭代器是否到达末尾。
#include <iostream>
class MyContainer {
public:
MyContainer(int* array, size_t size) : data(array), size(size) {}
// 迭代器类
class Iterator {
public:
Iterator(int* ptr) : current(ptr) {}
int& operator*() { return *current; } // 解引用
Iterator& operator++() { // 前置递增
current++;
return *this;
}
bool operator!=(const Iterator& other) const { // 比较
return current != other.current;
}
private:
int* current;
};
Iterator begin() { return Iterator(data); } // 返回起始迭代器
Iterator end() { return Iterator(data + size); } // 返回末尾迭代器
private:
int* data;
size_t size;
};
int main() {
int arr[] = {1, 2, 3, 4, 5};
MyContainer container(arr, 5);
for (int elem : container) {
std::cout << elem << " "; // 输出: 1 2 3 4 5
}
return 0;
}
STL 中的大部分容器都已经实现了begin、end接口,可以使用for循环,std::stack、std::queue 和 std::priority_queue 虽然内部使用容器,但它们本身不直接支持迭代器,因此不能直接在 for 循环中使用。
[]
: 不捕获任何变量。[=]
: 按值捕获所有变量(不能修改外部变量)。[&]
: 按引用捕获所有变量(可以修改外部变量)。[x]
: 按值捕获指定变量。[&x]
: 按引用捕获指定变量。[this]
: 捕获当前对象的 this 指针,允许访问类的成员。[=, &x]
: 按值捕获所有变量,但按引用捕获 x。[&, x]
: 按引用捕获所有变量,但按值捕获 x。[=, this]
: 按值捕获所有变量并捕获当前对象。
C++14 Lambda 表达式的捕获列表支持直接移动捕获。C++11 只允许按值或按引用捕获外部变量,而 C++14 支持通过移动语义捕获。
示例:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
std::cout << *p << std::endl; // 打印 42
};
简介
避免高危函数的最佳实践
- 使用带有长度限制的函数版本(如 strncpy、snprintf)。
- 在使用动态内存管理时,确保正确处理分配失败和释放内存。
- 使用安全库(如 C++ 标准库的 std::string、std::vector 等)来避免手动管理内存。
内存管理函数
malloc(size_t size)
从堆分配指定大小的内存块。malloc 通常调用操作系统的内存管理接口(如 brk 或 mmap)来分配内存。它维护一个内部的堆管理数据结构来追踪已分配和未分配的内存块。简化的伪代码可能如下:
void* malloc(size_t size) {
if (size == 0) {
return nullptr;
}
// 查找足够大的未分配块
Block* block = find_free_block(size);
if (!block) {
// 分配新的内存块
block = request_memory_from_os(size);
}
block->allocated = true;
return (void*)(block + 1); // 跳过块的元数据,返回数据区指针
}
calloc(size_t num, size_t size):分配一块内存并将其初始化为 0。 realloc(void *ptr, size_t size):重新调整之前分配的内存块大小。 free(void *ptr):释放动态分配的内存。
字符串处理函数
strcpy(char *dest, const char *src)
复制字符串。缓冲区溢出:如果目标缓冲区的大小不足以容纳源字符串及其终止符,可能会导致数据被写入非法内存区域,从而引发崩溃或安全漏洞。
char* strcpy(char* dest, const char* src) {
char* temp = dest;
while ((*dest++ = *src++) != '\0');
return temp;
}
strncpy(char *dest, const char *src, size_t n)
复制指定长度的字符串。
strcat(char *dest, const char *src)
将源字符串追加到目标字符串后。
strncat(char *dest, const char *src, size_t n)
将最多 n 个字符的源字符串追加到目标字符串后。
strlen(const char *str)
计算字符串长度,不包括终止符 \0。无直接风险,但需要确保传入的字符串以 \0 结束,否则可能会访问非法内存
strcmp(const char *str1, const char *str2)
比较两个字符串。它按字符逐一比较,返回结果表示比较大小。一般情况下,strcmp 本身不会造成缓冲区溢出等问题,但如果传递的参数是未终止的字符串,可能会导致访问非法内存区域。
int strcmp(const char* str1, const char* str2) {
while (*str1 && (*str1 == *str2)) {
str1++;
str2++;
}
return *(unsigned char*)str1 - *(unsigned char*)str2;
}
strncmp(const char *str1, const char *str2, size_t n):比较前 n 个字符的字符串。 strchr(const char *str, int c):在字符串中查找字符首次出现的位置。 strrchr(const char *str, int c):在字符串中查找字符最后一次出现的位置。 strstr(const char *haystack, const char *needle):查找子字符串。
sprintf(char *str, const char *format, ...)
格式化字符串。它会将格式化数据写入目标缓冲区,但不检查缓冲区是否有足够的空间,容易导致缓冲区溢出。 替代方案:使用 snprintf。
snprintf(char *str, size_t size, const char *format, ...):格式化字符串并指定缓冲区大小。
文件操作函数
fopen(const char *filename, const char *mode):打开文件。 fclose(FILE *stream):关闭文件。 fread(void *ptr, size_t size, size_t count, FILE *stream):从文件中读取数据。 fwrite(const void *ptr, size_t size, size_t count, FILE *stream):向文件中写入数据。 fgets(char *str, int n, FILE *stream):从文件中读取一行数据。 fputs(const char *str, FILE *stream):将字符串写入文件。 fprintf(FILE *stream, const char *format, ...):格式化输出到文件。 fscanf(FILE *stream, const char *format, ...):从文件中读取格式化输入。 ftell(FILE *stream):获取当前文件指针的位置。 fseek(FILE *stream, long offset, int whence):调整文件指针位置。 rewind(FILE *stream):将文件指针重新定位到文件开头。
数学函数
abs(int n):计算整数的绝对值。 fabs(double x):计算浮点数的绝对值。 pow(double x, double y):计算 x 的 y 次幂。 sqrt(double x):计算平方根。 sin(double x):计算正弦值。 cos(double x):计算余弦值。 tan(double x):计算正切值。 log(double x):计算自然对数。 exp(double x):计算 e 的 x 次幂。 ceil(double x):向上取整。 floor(double x):向下取整。 rand():生成随机数。 srand(unsigned int seed):设置随机数种子。
时间函数
time(time_t *timer):获取当前时间。 clock():获取程序运行时间。 difftime(time_t end, time_t begin):计算两个时间点的差值。 gmtime(const time_t *timer):将时间转换为 UTC 时间。 localtime(const time_t *timer):将时间转换为本地时间。 strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr):格式化时间输出。
内存操作函数
memcpy(void *dest, const void *src, size_t n)
复制内存块,用于将一块内存复制到另一块内存。由于不检查源和目标内存的大小,可能会导致缓冲区溢出。
现代实现可能会优化为按字(4 字节)或双字(8 字节)复制,以提高效率。简化版的实现可以如下:·
void* memcpy(void* dest, const void* src, size_t n) {
char* d = (char*)dest;
const char* s = (const char*)src;
// 按字节复制 n 个字节
while (n--) {
*d++ = *s++;
}
return dest;
}
替代方案:确保源和目标内存区域大小适合。
memmove(void *dest, const void *src, size_t n)
安全地复制内存块,允许重叠。memmove 与 memcpy 类似,也是用于内存复制,但它能处理源地址和目标地址重叠的情况。它确保正确处理重叠内存区域中的数据,避免数据覆盖问题。
void* memmove(void* dest, const void* src, size_t n) {
char* d = (char*)dest;
const char* s = (const char*)src;
if (d < s) {
// 如果目标地址小于源地址,从前向后复制(类似于 memcpy)
while (n--) {
*d++ = *s++;
}
} else {
// 如果目标地址大于源地址,从后向前复制,避免覆盖
d += n;
s += n;
while (n--) {
*(--d) = *(--s);
}
}
return dest;
}
memset(void *str, int c, size_t n)
将内存块设置为指定的值。
如果设置的值可以扩展为多个字节(例如,所有字节都为 0 或 0xFF),可以按字、双字甚至更大的数据块进行设置,以提高速度。
现代硬件可能会支持矢量化操作,可以将多个字节的设置指令打包成一个操作,进一步提升性能。
void* memset(void* str, int c, size_t n) {
unsigned char* p = (unsigned char*)str;
while (n--) {
*p++ = (unsigned char)c;
}
return str;
}
memcmp(const void *str1, const void *str2, size_t n)
用于逐字节比较两块内存区域的内容。它返回一个整数,用于表示两块内存是否相同及其相对大小。
int memcmp(const void* ptr1, const void* ptr2, size_t n) {
const unsigned char* p1 = (const unsigned char*)ptr1;
const unsigned char* p2 = (const unsigned char*)ptr2;
while (n--) {
if (*p1 != *p2) {
return *p1 - *p2;
}
p1++;
p2++;
}
return 0; // 如果所有字节都相同,返回 0
}
动态库加载
dlopen(const char *filename, int flag):动态加载共享库。 dlsym(void *handle, const char *symbol):获取共享库中的符号地址。 dlclose(void *handle):关闭动态加载的共享库。 dlerror():获取动态库相关的错误信息。
C++ 标准库
std::string:C++ 字符串类,提供动态管理字符串的功能。 std::vector:动态数组类,支持自动扩展和缩小。 std::map:关联容器,用于存储键值对。 std::unordered_map:哈希表实现的关联容器。 std::sort:排序算法。 std::find:查找算法。 std::unique_ptr:独占所有权的智能指针。 std::shared_ptr:共享所有权的智能指针。 std::mutex:互斥锁,用于多线程编程。
输入输出函数
printf(const char *format, ...):格式化输出到标准输出。 scanf(const char *format, ...):格式化输入。 putchar(int char):输出单个字符。 getchar():读取单个字符。 puts(const char *str):输出字符串并换行。
gets(char *str)
读取一行输入(高危函数,建议使用 fgets)。该函数从标准输入读取字符串,不做任何缓冲区大小检查,极易造成缓冲区溢出。 替代方案:使用 fgets 来指定读取的缓冲区大小。
信号处理
signal(int signum, void (*handler)(int)):设置信号处理函数。 raise(int sig):发送信号。 abort():异常终止程序。 exit(int status):正常终止程序。
基本概念
C++中的模板编程是一种强大且灵活的编程技术,允许编写通用代码。模板通过参数化数据类型或操作,使得程序可以处理不同类型的数据,而无需重复编写类似代码。这种技术广泛应用于泛型编程,例如C++标准模板库(STL)中的容器、迭代器和算法就是基于模板实现的。
函数模板允许编写适用于不同类型的通用函数。你可以用一个模板参数定义一个函数,而不必为每种类型重写函数。
函数模板
#include <iostream>
using namespace std;
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
cout << "Add integers: " << add(5, 3) << endl; // 调用时推断 T 为 int
cout << "Add doubles: " << add(2.5, 3.1) << endl; // 调用时推断 T 为 double
return 0;
}
template <typename T>
:这里T是一个类型模板参数,表示这个函数适用于任何类型T。- 函数add可以接收任意相同类型的两个参数,并返回它们的和。编译器会根据调用时的参数类型推导出T。
类模板
#include <iostream>
using namespace std;
template <typename T>
class Box {
T value;
public:
Box(T val) : value(val) {}
T getValue() { return value; }
};
int main() {
Box<int> intBox(123); // Box 类的 int 实例
Box<string> strBox("Hello"); // Box 类的 string 实例
cout << "Int Box: " << intBox.getValue() << endl;
cout << "String Box: " << strBox.getValue() << endl;
return 0;
}
模板特化
模板的一个强大功能是模板特化。模板特化允许针对某个特定类型进行特殊化处理,即可以为某个类型提供一个特殊的实现。
全特化
全特化是指对模板的特定类型进行完全特化处理。
#include <iostream>
using namespace std;
template <typename T>
class Box {
public:
T value;
Box(T val) : value(val) {}
void print() { cout << "Value: " << value << endl; }
};
// 对 char* 类型进行特化
template <>
class Box<char*> {
public:
char* value;
Box(char* val) : value(val) {}
void print() { cout << "String: " << value << endl; }
};
int main() {
Box<int> intBox(123);
Box<char*> strBox("Hello Template");
intBox.print();
strBox.print(); // 调用特化版本
return 0;
}
Box<char*>
是Box类的特化版本,针对char*
类型的模板进行了不同的实现。这样可以在处理字符串时,提供与一般模板不同的行为。
偏特化
偏特化允许我们对某些模板参数进行特化,而不需要对所有模板参数进行特化。
#include <iostream>
using namespace std;
// 泛型类模板
template <typename T1, typename T2>
class Pair {
public:
T1 first;
T2 second;
Pair(T1 f, T2 s) : first(f), second(s) {}
void print() { cout << first << ", " << second << endl; }
};
// 偏特化: 当两个类型相同时,采用特化版本
template <typename T>
class Pair<T, T> {
public:
T first;
T second;
Pair(T f, T s) : first(f), second(s) {}
void print() { cout << "Same type pair: " << first << ", " << second << endl; }
};
int main() {
Pair<int, double> p1(1, 2.5);
Pair<int, int> p2(3, 4); // 调用偏特化版本
p1.print();
p2.print(); // 调用偏特化版本
return 0;
}
模板元编程
模板不仅仅用于定义数据结构和函数,还可以进行编译期的计算,称为模板元编程。模板元编程允许通过递归模板在编译期执行逻辑运算,减少运行时的开销。
编译期阶乘计算
#include <iostream>
using namespace std;
// 模板递归计算阶乘
template <int N>
struct Factorial {
enum { value = N * Factorial<N - 1>::value };
};
// 基例:Factorial<0> 为 1
template <>
struct Factorial<0> {
enum { value = 1 };
};
int main() {
cout << "Factorial<5>::value = " << Factorial<5>::value << endl;
return 0;
}
Factorial<N>
通过模板递归计算阶乘,直到基例Factorial<0>
终止递归。这一计算是在编译时完成的,因此在运行时没有任何额外的开销。
Factorial<5>::value
会展开成5 * 4 * 3 * 2 * 1
,并在编译时得出结果。
变参模板
变参模板允许传递任意数量的模板参数,使得模板更加灵活。
#include <iostream>
using namespace std;
// 变参模板,处理多个参数
template <typename T, typename... Args>
void print(T first, Args... args) {
cout << first << " ";
if constexpr(sizeof...(args) > 0) {
print(args...); // 递归调用
}
}
int main() {
print(1, 2.5, "Hello", 'c'); // 传递多个不同类型的参数
return 0;
}
template <typename T, typename... Args>
中的Args...
表示可以接受多个模板参数。
print函数递归处理每个参数,直到参数耗尽为止。
SFINAE(Substitution Failure Is Not An Error)
SFINAE是C++模板元编程中非常重要的概念。当模板参数替换失败时,并不导致编译错误,而是选择其他可用的重载版本。这通常用于特性检测和选择性模板实例化。
示例
#include <iostream>
#include <type_traits>
using namespace std;
// 通用模板
template <typename T>
typename enable_if<is_integral<T>::value, T>::type
foo(T t) {
cout << "Integer type: " << t << endl;
return t;
}
// 重载版本,用于非整数类型
template <typename T>
typename enable_if<!is_integral<T>::value, T>::type
foo(T t) {
cout << "Non-integer type: " << t << endl;
return t;
}
int main() {
foo(10); // 调用整数版本
foo(3.14); // 调用非整数版本
return 0;
}
enable_if
结合is_integral
用于条件编译。根据传递的类型是否为整数类型,编译器会选择不同的模板实例化版本。
基本概念
在C++中,移动语义(Move Semantics)是为了提高性能而引入的一种机制,特别是在涉及大数据结构或需要频繁复制对象的场景中。移动语义允许“移动”资源,而不是“复制”资源,从而避免了不必要的深拷贝操作。
通常,当我们复制一个对象时,会分配新的内存并复制该对象的所有内容。而移动语义则允许我们将资源的所有权从一个对象转移到另一个对象,而不进行深拷贝。这样可以显著提高性能,尤其是在处理动态分配的大数据结构时。
右值引用(Rvalue Reference)是实现移动语义的基础。右值引用使用符号&&,它只能绑定到右值(临时对象)上,表示对象的资源可以被“移动”。
如果一个对象是右值,可以将它的资源直接“搬”到另一个对象,而不再执行复制操作。在移动之后,被移动的对象处于有效但未指定的状态,通常会释放它的资源,或设置为空状态。
示例
#include <iostream>
#include <utility> // for std::move
#include <cstring>
class MyString {
private:
char* data; // 用于存储字符串的指针
size_t length;
public:
// 构造函数
MyString(const char* str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
std::cout << "Constructing MyString: " << data << std::endl;
}
// 拷贝构造函数 (深拷贝)
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
std::cout << "Copying MyString: " << data << std::endl;
}
// 移动构造函数 (移动语义)
MyString(MyString&& other) noexcept {
data = other.data; // "偷取"资源
length = other.length;
// 将源对象置于有效但未指定的状态
other.data = nullptr;
other.length = 0;
std::cout << "Moving MyString (constructor): " << data << std::endl;
}
// 移动赋值运算符
MyString& operator=(MyString&& other) noexcept {
std::cout << "Moving MyString (assignment): " << other.data << std::endl;
// 自我赋值检查
if (this != &other) {
// 释放已有的资源
delete[] data;
// "偷取"资源
data = other.data;
length = other.length;
// 将源对象置于有效但未指定的状态
other.data = nullptr;
other.length = 0;
}
return *this;
}
// 析构函数
~MyString() {
if (data != nullptr) {
std::cout << "Destroying MyString: " << data << std::endl;
delete[] data;
}
}
};
int main() {
MyString str1("Hello, World!");
// 使用移动构造函数,在创建对象时将右值的资源转移到新对象。
MyString str2 = std::move(str1); // 触发移动构造函数
MyString str3("Temporary String");
// 使用移动赋值运算符,是在对象已经存在的情况下,将右值的资源转移到已有
str3 = std::move(str2); // 触发移动赋值运算符
return 0;
}
- 移动构造函数在
MyString str2 = std::move(str1);
这一行被调用。此时str2是一个新创建的对象,直接从str1“偷取”资源,而不需要处理任何已有的资源。 - 移动赋值运算符在
str3 = std::move(str2);
这一行被调用。此时str3已经存在,移动赋值运算符首先需要释放str3之前持有的资源,然后再从str2那里“偷取”资源。
在C++中,左值(lvalue)和右值(rvalue)是与值的生命周期和表达式类型有关的概念。它们与C++11引入的右值引用和完美转发机制密切相关。以下是这些概念的详细解释和代码示例。
1. 左值(Lvalue)
左值是指持久存在的对象或内存位置,它可以出现在赋值操作符的左边。换句话说,左值可以被引用,并且有明确的地址。
示例
:
int x = 10; // x是一个左值,因为它可以被引用并且有内存地址
int& ref = x; // 可以通过引用来绑定左值
ref = 20; // 通过引用修改x的值
在上面的代码中,x 是一个左值,因为它有明确的地址并且可以被赋值或引用。
2. 右值(Rvalue)
右值是指一个临时值,通常是一个表达式的结果,并且没有明确的内存地址。右值不能出现在赋值操作符的左边,因为它们没有持久的存储空间。
int a = 5;
int b = 3;
int result = a + b; // a + b是一个右值,因为它是一个表达式的结果,没有具体的地址s
3. 右值引用(Rvalue Reference)
C++11引入了右值引用,用来绑定右值。右值引用使用&&语法,可以捕获和操作临时对象,从而避免不必要的复制。右值引用的主要应用之一是移动语义,即通过转移临时对象的资源来提高性能。
#include <iostream>
#include <utility> // for std::move
void process(int&& x) {
std::cout << "Processing rvalue: " << x << std::endl;
x = 30; // 对于左值move过来的右值引用是可以修改的, 当然这个不是右值引用(本身只是为了处理右值复制的问题)的专利,普通引用int也可以修改
}
int main() {
int a = 10;
process(std::move(a)); // std::move将a转换为右值
std::cout << a << std::endl;
process(20); // 20是右值,可以直接传递给右值引用
// output
// Processing rvalue: 10
// 30
// Processing rvalue: 20
}
4. 完美转发(Perfect Forwarding)
完美转发是指在模板中将参数精确地传递给另一个函数,保持其原始的左值或右值特性。它通常通过结合模板参数推导和右值引用来实现。
完美转发的主要工具是std::forward。std::forward确保在函数调用中参数的值类别(左值或右值)得以保留。
示例
:
#include <iostream>
#include <utility> // for std::forward
// 处理左值和右值的函数重载
void process(int& x) {
std::cout << "Processing lvalue: " << x << std::endl;
}
void process(int&& x) {
std::cout << "Processing rvalue: " << x << std::endl;
}
// 泛型函数,接收任意类型的参数并转发给另一个函数
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // 完美转发
}
int main() {
int a = 5;
wrapper(a); // 传递左值
wrapper(10); // 传递右值
}
说明
:
- 在wrapper函数中,
T&&
是一个万能引用(universal reference),可以绑定左值或右值。 std::forward<T>(arg)
保证了当传入的是左值时,arg会作为左值传递;当传入的是右值时,arg会作为右值传递,这就是完美转发。
总结
- 左值(lvalue) 是有具体地址的对象,可以被持久引用。
- 右值(rvalue) 是临时对象或表达式的结果,通常没有地址。
- 右值引用(rvalue reference) 是通过&&来引用右值的机制,主要用于移动语义,避免拷贝。
- 完美转发 用于保持参数的值类别(左值或右值)不变,通过std::forward实现。
这些特性有助于优化代码性能,尤其是在处理临时对象和资源转移时。
- 指令排序相关
- 内联函数(Inline Functions)
- 常量表达式(constexpr)
- 模板元编程(Template Metaprogramming)
- 循环展开(Loop Unrolling)
- 常量折叠(Constant Folding)
- 条件优化(Compile-time Conditions)
- 静态断言(static_assert)
- consteval和constinit
指令排序相关
volatile
关键字, 防止编译器对访问变量的指令进行优化,例如重新排序、合并或消除读取操作。
volatile int x;
- 告诉编译器,每次访问 volatile 变量时,都必须从内存中重新读取,不能进行缓存或优化。
- 主要用于多线程编程、硬件寄存器访问等场景,防止编译器假设变量不会被外部修改。
在多线程环境下,多个线程可能同时访问或修改同一个变量,如果编译器优化了对这个变量的访问,可能导致数据不一致或行为异常。在嵌入式系统中,硬件寄存器的值可能会在后台发生变化,因此必须确保对这些寄存器的访问不会被编译器优化掉。
memory_order + atomic
控制内存屏障,保证多线程环境下的指令和内存操作顺序。
#include <atomic>
std::atomic<int> x(0);
x.store(10, std::memory_order_relaxed); // 使用不同的内存顺序
在多核处理器上,不同线程对共享变量的操作可能会被重新排序,导致不一致性。atomic 和 memory_order 确保了多线程环境下的内存可见性和执行顺序。
内存屏障
防止编译器和CPU对特定内存操作进行重新排序。
#include <atomic>
std::atomic_thread_fence(std::memory_order_seq_cst); // 严格内存屏障
内存屏障可以阻止编译器或硬件对指令重新排序,确保操作的执行顺序符合预期,尤其在多线程和多核环境中。
asm volatile
防止编译器对特定的汇编指令进行优化或重新排序。
asm volatile("nop"); // 禁止优化此指令
std::launder
防止编译器对某些内存操作(特别是对象的重定位或placement new)进行优化。
#include <new>
int* p = new int(42);
std::launder(p);
std::launder 用于避免编译器对可能被重定位或重新构造的对象进行不安全的优化操作。
当使用 placement new 或其他低级内存操作时,编译器可能做出错误假设,std::launder 确保这些对象的合法访问。
内联函数(Inline Functions)
内联函数通过在调用处直接展开函数体,避免了函数调用的开销(如参数传递、栈帧操作)。在性能关键的代码中,尤其是小型函数的多次调用时,内联可以显著提升效率。
使用场景:
- 小型的、频繁调用的函数。
- 不适合大函数,因为会增大可执行文件的大小。
#include <iostream>
// 建议编译器内联
inline int square(int x) {
return x * x;
}
__attribute__((noinline)) void bar() { /* ... */ } // 明确禁止内联
int main() {
int a = 5;
std::cout << "Square of 5: " << square(a) << std::endl;
return 0;
}
在编译时,square(a)会被替换为a * a,避免了函数调用的开销。
- inline 关键字提示编译器可以尝试内联函数,但并非强制。编译器可以根据实际情况决定是否内联。
- noinline 属性明确告诉编译器不允许内联,防止它对函数进行优化处理。
常量表达式(constexpr)
constexpr是C++11引入的一种机制,用来在编译期计算表达式的值。它可以用于定义常量和函数,使得结果在编译期就已经确定。通过提前计算,可以减少运行时的计算负担。
使用场景:
- 需要在编译期确定的常量或运算。
- 用于在模板元编程或编译期优化中提升效率。
#include <iostream>
constexpr int factorial(int n) {
return (n <= 1) ? 1 : (n * factorial(n - 1));
}
int main() {
constexpr int result = factorial(5); // 在编译期计算
std::cout << "Factorial of 5: " << result << std::endl;
return 0;
}
factorial(5)的结果在编译期计算,避免了运行时计算,直接生成常量值。
模板元编程(Template Metaprogramming)
模板元编程是C++的一种强大特性,允许在编译期执行复杂的运算。通过模板递归、constexpr和其他编译期机制,可以在编译期生成高效的代码,避免运行时开销。
使用场景:
- 需要进行编译期复杂运算或条件判断。
- 用于生成高效、通用的代码,避免重复的代码实现。
#include <iostream>
template<int N>
struct Factorial {
static const int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<1> {
static const int value = 1;
};
int main() {
std::cout << "Factorial of 5: " << Factorial<5>::value << std::endl;
return 0;
}
Factorial<5>::value在编译期递归展开,计算出结果120,避免了运行时计算。
循环展开(Loop Unrolling)
循环展开是一种通过在编译期将循环体重复多次,减少循环控制逻辑的开销的优化技术。编译器会根据循环次数的固定性和大小,展开循环以减少分支跳转的代价。
使用场景:
- 循环次数较小且固定。
- 需要减少循环的分支跳转开销。
#include <iostream>
void sumArray(const int* arr, int size) {
int sum = 0;
for (int i = 0; i < size; i += 4) {
sum += arr[i];
sum += arr[i + 1];
sum += arr[i + 2];
sum += arr[i + 3];
}
std::cout << "Sum: " << sum << std::endl;
}
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8};
sumArray(arr, 8);
return 0;
}
通过手动展开循环,每次迭代处理多个元素,减少了循环跳转的次数,提高了处理效率。编译器有时也会自动进行循环展开。
常量折叠(Constant Folding)
常量折叠是编译器在编译期计算常量表达式的值,并将结果直接嵌入生成的代码中,避免运行时的计算。
使用场景:
- 常量之间的运算。
- 用于表达式中的值在编译期就可以确定的场景。
#include <iostream>
int main() {
int x = 2 * 3 + 5; // 这个表达式会在编译期被计算
std::cout << "Result: " << x << std::endl;
return 0;
}
2 * 3 + 5的值在编译期直接计算为11,避免了运行时的计算。
条件优化(Compile-time Conditions)
利用模板或constexpr进行条件选择,允许编译期根据条件生成不同的代码路径,避免不必要的分支判断。
使用场景:
- 条件判断可以在编译期确定。
- 避免在运行时执行多余的分支判断。
#include <iostream>
template<bool Condition>
constexpr int choose() {
if constexpr (Condition) {
return 42;
} else {
return 0;
}
}
int main() {
constexpr int result = choose<true>(); // 编译期选择
std::cout << "Result: " << result << std::endl;
return 0;
}
if constexpr允许在编译期选择分支,未选择的分支会被完全忽略,不会生成代码。
静态断言(static_assert)
static_assert允许在编译期对某些条件进行验证,确保代码在编译阶段就捕获潜在的错误。这有助于提高代码的健壮性和优化编译期行为。
使用场景:
- 在编译期确保某些编译条件成立。
- 防止无效类型或错误配置进入编译流程。
#include <iostream>
template<typename T>
constexpr void checkType() {
static_assert(sizeof(T) <= 4, "Type size is too large!");
}
int main() {
checkType<int>(); // 编译通过
// checkType<double>(); // 编译失败,double的大小超过4字节
return 0;
}
static_assert在编译期进行类型检查,防止不符合要求的代码进入编译流程。
consteval和constinit
C++20 引入了两个新的关键字:consteval 和 constinit,用于改进编译时常量表达式的处理。
- consteval:声明一个函数为 consteval,意味着这个函数只能在编译时被调用,任何试图在运行时调用它的行为都会导致编译错误。consteval 强调了编译时求值的必要性,确保调用该函数的所有表达式在编译时都能求值,增强了类型安全性。
- constinit:当使用 constinit 声明一个变量时,编译器会确保该变量在定义时初始化,并且只允许常量表达式作为其初始值。constinit 防止了可能的未初始化常量的运行时行为,确保在使用该变量之前,它已经被初始化并且是常量的。
示例:
consteval int square(int n) {
return n * n;
}
constinit int value = square(4); // 确保在编译时初始化
这些特性增强了对常量表达式的控制,避免了潜在的运行时错误。
概览
- 什么是虚函数,有什么作用?
- 纯虚函数,为什么需要纯虚函数?
- 为什么需要虚析构函数,什么时候不需要?
- 内联函数、构造函数、静态成员函数可以是虚函数吗?
- 构造函数中可以调用虚函数吗?
- 为什么需要虚继承?虚继承实现原理解析
- 虚函数是针对类还是针对对象的?
- 同一个类的两个对象的虚函数表是如何维护的?
在C++中,虚函数(virtual function)是面向对象编程的一个核心概念,它允许通过基类指针或引用来调用派生类的重写方法,实现动态多态(dynamic polymorphism)。虚函数的背后涉及虚函数表(vtable)、指针机制以及一些运行时的操作。
虚函数的基本概念与作用
虚函数是基类中声明为virtual的成员函数,它允许派生类重写该函数,并支持通过基类的指针或引用调用派生类的重写函数。
作用
:
- 虚函数实现了动态多态,允许基类指针或引用在运行时调用派生类的重写函数,而不是基类的版本。
#include <iostream>
class Base {
public:
virtual void show() {
std::cout << "Base class show() called" << std::endl;
}
};
class Derived : public Base {
public:
void show() override { // 重写虚函数
std::cout << "Derived class show() called" << std::endl;
}
};
int main() {
Base* ptr = new Derived(); // 基类指针指向派生类对象
ptr->show(); // 调用派生类的show(),而不是Base的show()
// 输出 Derived class show() called
delete ptr;
}
在这个例子中,Base类的show()是虚函数,当通过基类指针ptr调用时,实际调用的是派生类Derived的show(),这就是虚函数的作用——动态分派。
纯虚函数与抽象类
纯虚函数(pure virtual function)是指在基类中只声明而不定义的虚函数,形式是virtual void func() = 0;
。一个包含纯虚函数的类称为抽象类,抽象类不能实例化,只能被继承。
class Base {
public:
virtual void show() = 0; // 纯虚函数
};
class Derived : public Base {
public:
void show() override {
std::cout << "Derived class show() called" << std::endl;
}
};
int main() {
// Base obj; // 错误!抽象类不能实例化
Base* ptr = new Derived();
ptr->show(); // 调用派生类的show()
delete ptr;
}
虚析构函数的必要性
如果一个类中存在虚函数,通常也需要将析构函数声明为虚函数。这是为了确保通过基类指针或引用删除对象时,能够正确调用派生类的析构函数,防止资源泄漏。
为什么需要虚析构函数: 如果基类的析构函数不是虚函数,那么通过基类指针删除派生类对象时,只会调用基类的析构函数,导致派生类的资源没有正确释放。
class Base {
public:
virtual ~Base() {
std::cout << "Base destructor called" << std::endl;
}
};
class Derived : public Base {
public:
~Derived() {
std::cout << "Derived destructor called" << std::endl;
}
};
int main() {
Base* ptr = new Derived(); // 基类指针指向派生类对象
delete ptr; // 调用虚析构函数,确保派生类析构函数被调用
// output
// Derived destructor called
// Base destructor called
}
如果没有虚析构函数,Derived类的析构函数将不会被调用,导致资源泄漏。
什么时候不需要虚析构函数
:
当类不打算用于继承,或者不打算通过基类指针删除派生类对象时,虚析构函数可以省略。普通的类(非多态类)不需要虚析构函数。
构造函数中可以调用虚函数吗?
不可以。在构造函数中调用虚函数时,虚函数的动态分派机制不会工作。因为在构造基类对象时,派生类部分还未构造完成,此时即便调用的是虚函数,调用的也是基类的版本。
class Base {
public:
Base() { show(); } // 构造函数中调用虚函数
virtual void show() {
std::cout << "Base show() called" << std::endl;
}
};
class Derived : public Base {
public:
void show() override {
std::cout << "Derived show() called" << std::endl;
}
};
int main() {
Derived obj; // 调用Derived的构造函数
// output
// Base show() called
}
即使派生类Derived重写了show(),构造函数中调用的仍然是基类的show()。
哪些函数不能是虚函数
- 内联函数(inline function):理论上内联函数可以是虚函数,但实际中,虚函数需要动态分派,因此通常不会被内联。编译器无法在运行时决定调用哪个版本的函数,所以虚函数的内联机会较少。
- 构造函数:构造函数不能是虚函数,因为在对象构造时无法进行动态分派。构造函数的职责是创建对象,虚函数的作用是在对象创建完成后进行动态分派。
- 静态成员函数:静态成员函数不能是虚函数,因为它们与具体的对象无关。虚函数依赖于对象的动态类型,而静态函数属于类本身,不依赖于对象实例。
为什么需要虚继承?
虚继承用于解决多重继承中的菱形继承问题,即当多个派生类从同一个基类继承时,可能导致基类被多次继承,从而引发二义性或资源冗余。虚继承通过让所有派生类共享基类的唯一实例来解决这个问题。
class Base {
public:
int value;
};
class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};
class Final : public Derived1, public Derived2 {};
int main() {
Final obj;
obj.value = 10; // 没有二义性,只有一个Base子对象
}
在上面的例子中,如果不使用虚继承,Final类将有两个Base的副本,导致value的二义性。
虚继承的实现原理
虚继承通过引入**虚基类指针(virtual base pointer, vptr)**来跟踪共享的基类实例。派生类在内存布局中包含一个指向虚基类实例的指针,所有派生类都指向同一个基类实例,避免重复继承。
虚函数表(vtable)的维护
每个具有虚函数的类在编译时会生成一个虚函数表(vtable),表中存储了该类的虚函数指针。每个对象会有一个虚函数表指针(vptr),指向该类的虚函数表。
同一个类的两个对象的虚函数表
:
对于同一个类的多个对象,它们共享相同的虚函数表(vtable),但每个对象都有自己的虚函数表指针(vptr),指向相同的vtable。
虚函数是针对类还是对象的
:
虚函数是针对类的。虚函数表是类级别的,所有对象共享同一个虚函数表,但调用虚函数时,是根据对象的动态类型来选择合适的函数。这就是动态多态的核心机制。
class Base {
public:
virtual void show() { std::cout << "Base show() called" << std::endl; }
};
class Derived : public Base {
public:
void show() override { std::cout << "Derived show() called" << std::endl; }
};
int main() {
Base b;
Derived d;
Base* ptr1 = &b;
Base* ptr2 = &d;
ptr1->show(); // 调用Base的show()
ptr2->show(); // 调用Derived的show()
// output
// Base show() called
// Derived show() called
}
在这个例子中,Base和Derived类都有各自的虚函数表,ptr1指向基类对象,调用基类的show(),ptr2指向派生类对象,调用派生类的show()。
如果拿到虚函数表的储存地址,是否可以改写虚函数表的内容?
虽然可以修改虚函数表的内容,但这种操作是非常危险的,如果将虚函数表的某个条目改为无效地址,调用虚函数时会导致崩溃。改写虚函数表后,程序行为变得不可预测,可能导致奇怪的结果、错误调用、数据损坏等问题。
#include <iostream>
#include <cstring>
class Base {
public:
virtual void func1() {
std::cout << "Base func1" << std::endl;
}
virtual void func2() {
std::cout << "Base func2" << std::endl;
}
};
class Derived : public Base {
public:
void func1() override {
std::cout << "Derived func1" << std::endl;
}
void func2() override {
std::cout << "Derived func2" << std::endl;
}
};
// 一个假的函数,伪装成虚函数
void hacked_func() {
std::cout << "Hacked function called!" << std::endl;
}
int main() {
Derived obj;
// 获取虚函数表指针
// vptr通常是对象的前8个字节(或者32位系统的前4个字节)
long long* vptr = *(long long**)(&obj);
// 打印虚函数表内容
std::cout << "Before hacking:" << std::endl;
typedef void(*FuncPtr)();
FuncPtr original_func1 = (FuncPtr)vptr[0];
original_func1(); // 调用虚函数表的第一个函数,应该调用Derived::func1
// 修改虚函数表的第一个函数指针
vptr[0] = (long long)(&hacked_func);
// 调用虚函数表第一个函数,应该调用伪造的hacked_func
std::cout << "After hacking:" << std::endl;
original_func1 = (FuncPtr)vptr[0];
original_func1(); // 调用hacked_func
// output
// Before hacking:
// Derived func1
// After hacking:
// Hacked function called!
return 0;
}
概览
- 指针
- 迭代器与普通指针有什么区别
- C++的智能指针及其原理
- 悬挂指针和野指针有什么区别?
- 指针常量和常量指针的区别
- 指针和引用有什么区别呢?
- 如何避免悬挂指针?
指针的基础概念
指针是存储内存地址的变量。它指向一个特定类型的对象或变量,并且通过解引用操作(*)可以访问该地址上的值。
示例
:
int a = 10;
int* p = &a; // p是指向a的指针
std::cout << *p; // 解引用p,输出a的值:10
指针的用途很多,例如动态内存分配、函数参数传递、数组操作等。
指针常量与常量指针
- 指针常量(constant pointer):指针本身是常量,指向的地址不能改变,但指向的内容可以改变。
- 常量指针(pointer to constant):指针指向的对象是常量,不能通过指针修改该对象的值,但可以改变指针指向的地址。
int a = 5;
int b = 10;
// 指针常量:指向的地址不能改变
int* const ptr1 = &a;
*ptr1 = 20; // 可以修改a的值
// ptr1 = &b; // 错误,ptr1不能改变指向
// 常量指针:指向的值不能改变
const int* ptr2 = &a;
ptr2 = &b; // 可以改变ptr2的指向
// *ptr2 = 30; // 错误,不能修改指向对象的值
指针和引用的区别
- 指针可以为空,也可以重新指向其他对象;指针的大小是固定的。
- 引用必须在定义时初始化,且不能重新绑定到其他对象。引用实际上是对象的别名,不占用额外内存。
主要区别:指针更灵活,允许指向不同的对象;引用更简单,用于定义后不会变化的别名。
int a = 5;
int* ptr = &a; // 指针
int& ref = a; // 引用
ref = 10; // 通过引用修改a的值
ptr = nullptr; // 指针可以为空
// ref = &b; // 引用不能重新绑定
nullptr 的含义是什么?
nullptr 是C++11引入的,用来统一表示“空指针”概念。它替代了之前使用的 NULL 宏定义。
- 类型安全: 与旧的 NULL 不同,nullptr 有明确的类型,是 std::nullptr_t 类型的常量值,它可以自动转换为任何指针类型,但它不是整数(这避免了 NULL 被解释为整数的歧义)。nullptr 只能用于指针类型,不能用于非指针类型的场景。
- 表示空指针: nullptr 代表一个明确的“空指针”,即它不指向任何有效的内存地址。
指针的基本注意事项
- 解引用空指针:在解引用指针之前,确保它指向有效的内存,否则会导致运行时错误(如段错误)。
- 初始化指针:指针必须初始化(如nullptr或指向有效内存),否则可能成为悬挂指针或野指针。
- 动态内存管理:在使用new分配的内存时,必须记得用delete释放,防止内存泄漏。
悬挂指针与野指针
- 悬挂指针(dangling pointer):指向已经被释放或销毁的内存地址。
- 野指针(wild pointer):未初始化的指针,指向随机的内存位置。
int* ptr = new int(5);
delete ptr; // 释放内存
// 此时ptr是悬挂指针,因为它指向的内存已经释放
ptr = nullptr; // 通过将指针置为nullptr避免悬挂指针
如何避免悬挂指针?
- 使用智能指针:智能指针自动管理内存,避免悬挂指针。
- 设置指针为nullptr:在释放内存后,将指针置为nullptr,避免误用。
- 尽量减少使用原始指针:优先使用智能指针和引用,减少直接使用原始指针的场景。
- 注意作用域:确保指针的作用域与其指向的对象保持一致,避免指向已经销毁的对象。
int* ptr = new int(10);
delete ptr;
ptr = nullptr; // 避免悬挂指针
智能指针及其原理
C++11引入了智能指针,它们自动管理内存,避免手动delete。主要有三种类型的智能指针:
- std::unique_ptr:独占所有权,指针不能共享,生命周期结束时自动释放内存。
- std::shared_ptr:允许多个智能指针共享同一个对象,使用引用计数来管理内存。引用计数为0时,自动释放内存。
- std::weak_ptr:与shared_ptr配合使用,不影响引用计数,防止循环引用。
#include <memory>
std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::cout << *ptr1 << std::endl; // 输出10
std::shared_ptr<int> ptr2 = std::make_shared<int>(20);
std::shared_ptr<int> ptr3 = ptr2; // 引用计数增加
std::cout << *ptr3 << std::endl; // 输出20
原理:智能指针通过构造函数和析构函数控制指针的生命周期。当智能指针超出作用域或引用计数为0时,自动释放所管理的内存,避免内存泄漏。
unique_ptr
- 独占所有权:某个对象只允许有一个智能指针来管理它,即对象的所有权是唯一的。当该指针被销毁时,所管理的对象会自动被释放。
- 适合使用场景:动态分配的资源不需要共享,或希望确保资源的唯一性,例如RAII模式下的资源管理。
注意事项
- 不能复制:std::unique_ptr的所有权是独占的,不能进行复制操作,但可以通过移动语义转移所有权。
- 适合动态分配对象时,避免忘记释放资源的情况。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor\n"; }
~MyClass() { std::cout << "MyClass destructor\n"; }
void display() { std::cout << "MyClass::display\n"; }
};
int main() {
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(); // 创建一个unique_ptr
ptr1->display(); // 通过ptr1调用对象的方法
// 不能复制unique_ptr,但可以移动
std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 移动所有权
if (ptr1 == nullptr) {
std::cout << "ptr1 is now nullptr\n"; // 移动后,ptr1为空
}
ptr2->display(); // ptr2接管了对象的所有权
// ptr2被销毁时,自动释放MyClass对象
return 0;
}
输出
MyClass constructor
MyClass::display
ptr1 is now nullptr
MyClass::display
MyClass destructor
shared_ptr
- 共享所有权:允许多个智能指针共享一个对象的所有权。当最后一个指针销毁时,所管理的对象才会被释放。
- 适合使用场景:多个对象或函数需要共享同一资源,比如多个模块之间共享的缓存或数据。
注意事项
- 引用计数:std::shared_ptr会维护一个引用计数,当有新的shared_ptr指向对象时,计数增加;当一个shared_ptr销毁时,计数减少。只有当计数为0时,才释放对象。
- 循环引用问题:当两个shared_ptr对象互相引用时,会导致引用计数无法归零,造成内存泄漏。需要搭配std::weak_ptr来避免循环引用。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor\n"; }
~MyClass() { std::cout << "MyClass destructor\n"; }
void display() { std::cout << "MyClass::display\n"; }
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(); // 创建shared_ptr
{
std::shared_ptr<MyClass> ptr2 = ptr1; // 共享所有权
std::cout << "ptr1 use count: " << ptr1.use_count() << '\n'; // 查看引用计数
std::cout << "ptr2 use count: " << ptr2.use_count() << '\n';
ptr2->display(); // 通过ptr2调用对象方法
} // ptr2出了作用域,引用计数减1
std::cout << "ptr1 use count after ptr2 goes out of scope: " << ptr1.use_count() << '\n';
ptr1->display(); // ptr1仍然可以使用
// ptr1被销毁时,自动释放MyClass对象
return 0;
}
输出
MyClass constructor
ptr1 use count: 2
ptr2 use count: 2
MyClass::display
ptr1 use count after ptr2 goes out of scope: 1
MyClass::display
MyClass destructor
weak_ptr
std::shared_ptr使用引用计数机制管理资源,但如果两个shared_ptr对象互相持有对方的引用,会形成循环引用,导致引用计数无法归零,资源永远无法释放。
解决方案
使用std::weak_ptr来打破循环引用。std::weak_ptr不参与引用计数,不会阻止所管理对象的销毁。它可以从shared_ptr构造,但不能直接使用,需要通过lock()方法临时提升为shared_ptr。
#include <iostream>
#include <memory>
class Node;
class Node {
public:
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用weak_ptr打破循环引用
~Node() { std::cout << "Node destroyed\n"; }
};
int main() {
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2; // node1指向node2
node2->prev = node1; // node2通过weak_ptr指向node1
return 0; // node1和node2出了作用域,自动销毁
}
输出
Node destroyed
Node destroyed
C++中的并发编程是指多个线程或任务同时执行,旨在提高程序的效率、响应性或处理多个任务。虽然并发编程可以带来性能提升,但它也引入了许多复杂的问题,如线程竞争(race conditions)
、死锁(deadlocks)
、内存可见性问题(memory visibility issues)
等。
并发编程的基本原理
在并发编程中,多个线程同时运行并访问共享资源。C++标准库提供了<thread>
库,允许我们轻松创建和管理线程。线程可以共享相同的内存地址空间,这意味着它们可以直接访问同一个变量。这种共享内存的特性可以提高性能,但同时也可能导致竞争条件等并发问题。
#include <iostream>
#include <thread>
void task(int id) {
std::cout << "Task " << id << " is running." << std::endl;
}
int main() {
std::thread t1(task, 1); // 启动线程t1
std::thread t2(task, 2); // 启动线程t2
t1.join(); // 等待线程t1执行完毕
t2.join(); // 等待线程t2执行完毕
return 0;
}
这段代码中,我们创建了两个线程t1和t2,它们并行执行相同的task函数。每个线程都会输出自己的任务ID。为了确保程序正常退出,我们使用了join(),使得主线程等待所有子线程结束。
并发问题
并发问题通常发生在多个线程访问共享资源时,如果没有合适的同步机制,可能会产生不可预测的行为。常见的并发问题包括:
- (1) 竞争条件(Race Condition)
当多个线程并发地读写相同的共享资源且没有适当的同步时,可能导致竞争条件。结果是,程序的输出依赖于线程的执行顺序,而这个顺序是无法预测的。
错误示例:竞争条件
#include <iostream>
#include <thread>
int counter = 0;
void increment(int iterations) {
for (int i = 0; i < iterations; ++i) {
++counter; // 不安全的递增操作
}
}
int main() {
std::thread t1(increment, 100000);
std::thread t2(increment, 100000);
t1.join();
t2.join();
std::cout << "Final counter value: " << counter << std::endl;
return 0;
}
在这个例子中,counter是两个线程共享的变量。多个线程同时对counter进行递增操作时会发生竞争条件,最终输出的结果会不一致,因为递增操作不是原子的(非原子操作可能会被多个线程同时访问并中断)。
(2) 死锁(Deadlock)
当多个线程相互等待对方持有的资源释放时,就会发生死锁。死锁会导致程序无法继续运行。
错误示例:死锁
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutex1, mutex2;
void task1() {
std::lock_guard<std::mutex> lock1(mutex1);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::lock_guard<std::mutex> lock2(mutex2); // 死锁
}
void task2() {
std::lock_guard<std::mutex> lock2(mutex2);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::lock_guard<std::mutex> lock1(mutex1); // 死锁
}
int main() {
std::thread t1(task1);
std::thread t2(task2);
t1.join();
t2.join();
return 0;
}
在这个例子中,task1持有mutex1并等待mutex2,而task2持有mutex2并等待mutex1,从而造成了死锁。
并发安全(线程安全)的常见方法
为了避免并发问题,我们需要引入适当的同步机制。C++标准库提供了多种工具来帮助我们实现线程安全。
- (1) 互斥量(Mutex)
互斥量是用来保证只有一个线程可以访问共享资源的工具。使用互斥量可以确保某个线程在访问共享资源时不会被其他线程打断。
正确示例:使用互斥量避免竞争条件
#include <iostream>
#include <thread>
#include <mutex>
int counter = 0;
std::mutex mtx;
void increment(int iterations) {
for (int i = 0; i < iterations; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 锁定互斥量
++counter; // 安全的递增操作
}
}
int main() {
std::thread t1(increment, 100000);
std::thread t2(increment, 100000);
t1.join();
t2.join();
std::cout << "Final counter value: " << counter << std::endl;
return 0;
}
使用std::mutex保证同一时间只有一个线程可以访问counter,从而避免竞争条件。
- (2) 原子操作(Atomic Operations)
C++提供了<atomic>
库,允许对某些基本类型执行原子操作。原子操作不需要显式的锁机制,通常更高效。
正确示例:使用原子变量
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> counter(0);
void increment(int iterations) {
for (int i = 0; i < iterations; ++i) {
++counter; // 原子递增操作
}
}
int main() {
std::thread t1(increment, 100000);
std::thread t2(increment, 100000);
t1.join();
t2.join();
std::cout << "Final counter value: " << counter.load() << std::endl;
return 0;
}
在这个例子中,counter是一个原子变量,递增操作是线程安全的,无需使用互斥量。
性能问题分析
在并发编程中,性能与安全性之间存在权衡。引入锁和同步机制可以保证线程安全,但会带来额外的性能开销。以下是一些需要考虑的性能问题:
- 锁的开销:频繁加锁和解锁会降低并发性能,特别是在竞争激烈的场合(多个线程频繁尝试获取锁)。
- 原子操作的开销:尽管原子操作比锁轻量,但它们也会引入一些性能开销,尤其是在复杂的数据结构上。
- 上下文切换:线程之间的切换会带来额外的系统开销,如果线程数量过多,上下文切换可能导致性能下降。
在C++中,理解对象的内存布局是编写高效代码、进行性能优化和调试的重要基础。不同类型的对象在内存中的布局各不相同,从基本类型、指针和引用到复杂的类和对象,C++编译器为它们在内存中分配空间。我们将循序渐进地讨论这些对象的内存布局,并提供相应的代码示例以帮助理解。
1. 基本类型(Primitive Types)
C++中的基本类型包括int、char、float、double等。这些类型的内存布局是固定的,并且它们通常被直接存储在栈(stack)上。
基本类型的内存布局
- int 通常占用4个字节(具体取决于平台和编译器)。
- char 通常占用1个字节。
- float 通常占用4个字节。
- double 通常占用8个字节。
#include <iostream>
int main() {
int a = 10;
char b = 'c';
float c = 3.14;
double d = 2.718;
std::cout << "Size of int: " << sizeof(a) << " bytes\n";
std::cout << "Size of char: " << sizeof(b) << " bytes\n";
std::cout << "Size of float: " << sizeof(c) << " bytes\n";
std::cout << "Size of double: " << sizeof(d) << " bytes\n";
return 0;
}
输出结果可能为:
Size of int: 4 bytes
Size of char: 1 byte
Size of float: 4 bytes
Size of double: 8 bytes
2. 指针(Pointer)
指针是用于存储内存地址的变量。指针的大小通常取决于平台的架构:
- 在32位系统上,指针通常占用4个字节(32位地址空间)。
- 在64位系统上,指针通常占用8个字节(64位地址空间)。
指针的大小与它指向的对象类型无关,关键在于操作系统的地址空间大小。
示例代码:
#include <iostream>
int main() {
int* p1 = nullptr; // 指向int类型的指针
double* p2 = nullptr; // 指向double类型的指针
std::cout << "Size of int pointer: " << sizeof(p1) << " bytes\n";
std::cout << "Size of double pointer: " << sizeof(p2) << " bytes\n";
return 0;
}
输出结果可能为:
Size of int pointer: 8 bytes
Size of double pointer: 8 bytes
3. 引用(Reference)
引用在C++中是某个变量的别名。虽然它在语法上类似于指针,但它本质上是不同的。引用的内存布局取决于编译器的实现。在大多数情况下,引用会被编译器实现为指针,因此它的大小通常与指针相同。
示例代码:
#include <iostream>
int main() {
int a = 5;
int& ref = a;
std::cout << "Address of a: " << &a << std::endl;
std::cout << "Address of ref: " << &ref << std::endl; // 与a的地址相同
return 0;
}
尽管引用与变量共享同一个地址,但编译器可能在内部将引用实现为指针。
4. 函数(Function)
在C++中,函数的内存布局相对较为抽象,因为函数通常存储在代码段(code segment)中。指向函数的指针在内存中存储着函数的入口地址。
函数指针示例:
#include <iostream>
void myFunction() {
std::cout << "Hello, world!" << std::endl;
}
int main() {
void (*funcPtr)() = &myFunction; // 函数指针
std::cout << "Address of function: " << reinterpret_cast<void*>(funcPtr) << std::endl;
funcPtr(); // 通过函数指针调用函数
return 0;
}
输出结果会显示函数的内存地址,并执行该函数。
5. 类(Class)
类在内存中的布局取决于其成员变量和方法。编译器会为类中的数据成员分配内存,而成员函数不会影响对象的大小(因为成员函数是共享的,不占用对象实例的内存空间)。
类的内存布局示例:
#include <iostream>
class MyClass {
public:
int a; // 占用4字节
char b; // 占用1字节
double c; // 占用8字节
};
int main() {
MyClass obj;
std::cout << "Size of MyClass: " << sizeof(obj) << " bytes\n";
return 0;
}
在这个例子中,MyClass对象的大小不是简单的相加,因为编译器可能会进行内存对齐。即使char只占1字节,编译器可能会填充额外的字节以保证对齐。
对齐与填充:
- 内存对齐(Memory Alignment):为了提高内存访问效率,编译器通常会把数据对齐到特定的边界。例如,32位系统中可能要求4字节对齐,64位系统可能要求8字节对齐。
- 填充字节(Padding Bytes):为了满足对齐要求,编译器会在成员变量之间插入一些空白字节。
示例输出可能是:Size of MyClass: 16 bytes
这说明编译器在char后面插入了3个字节的填充,以保证double按8字节对齐。
如果 A 这个对象对应的类是一个空类,那么 sizeof(A) 的值是多少?
在C++中,即使类是空的,sizeof 一个对象仍然不会是 0。一个空类在 C++ 中的 sizeof 值通常是 1 字节。这是为了确保每个实例都有一个唯一的地址。不同的编译器实现可能会有不同的结果,但通常会返回 1 字节。
6. 对象的内存布局
对象是类的实例,它在内存中存储着类的所有非静态数据成员。静态成员属于类,而不是某个具体的对象,因此不包含在对象的内存布局中。
多重继承和虚表 当类使用继承时,特别是使用多重继承或虚函数时,对象的内存布局会更加复杂。例如,如果一个类有虚函数,编译器会为每个对象分配一个指向虚表(vtable)的指针,称为虚指针(vptr)。虚表存储了虚函数的地址。
示例代码(虚函数和虚表):
#include <iostream>
class Base {
public:
virtual void func() {
std::cout << "Base function\n";
}
};
class Derived : public Base {
public:
void func() override {
std::cout << "Derived function\n";
}
};
int main() {
Base b;
Derived d;
std::cout << "Size of Base: " << sizeof(b) << " bytes\n";
std::cout << "Size of Derived: " << sizeof(d) << " bytes\n";
return 0;
}
由于类Base和Derived都有虚函数,编译器会为每个对象分配虚指针。通常,虚指针的大小等于一个普通指针的大小(例如,64位系统上为8字节)。
如果 A 是某一个类的指针,那么在它等于 nullptr 的情况下能直接调用它对应类的 public 函数吗?
这取决于该 public 函数是否访问了该对象的成员。如果该函数不访问对象成员,并且它是一个非虚函数,那么可以在 A == nullptr 的情况下调用它而不会出错。但如果函数访问了对象的成员(无论是直接还是间接),或者是虚函数(因为虚函数需要通过虚表指针调用),那么调用时会引发未定义行为。
#include <iostream>
#include <utility> // for std::forward
class A{
public:
void process(int x) {
std::cout << "Processing lvalue: " << x << std::endl;
}
};
int main() {
A* a = nullptr;
a->process(2);
// output
// Processing lvalue: 2
}
非成员访问的成员函数
: 当一个成员函数不访问该对象的成员变量时,它在本质上相当于一个普通的函数,只是函数签名上有一个隐式的 this 指针。这种函数不会依赖于指针所指向的对象内容。
- 当你调用一个函数,比如 ptr->foo(),编译器会将它转化为 foo(ptr),其中 ptr 是隐式传递的 this 指针。
- 如果 foo 函数内部并不访问成员变量,而是执行独立逻辑,传入的 nullptr 就不会导致问题,因为它实际上没有解引用 this 指针中的任何内容。
对于其他的,比如需要解引用this指针,比如调用虚函数涉及虚表指针的解引用,使用nullptr就会出问题了。
类型推断
主要包括auto
, decltype
, 模板类型推断几种
auto 关键字
auto是C++11引入的关键字,允许编译器根据右侧的表达式自动推断变量的类型。通过使用auto,我们可以避免显式地写出复杂的类型定义,编译器会根据表达式的类型来推断。
特点:
- auto只能用于声明和定义,不能用作函数的参数类型。
- 编译器根据初始化的表达式推断类型。
auto 推导规则的特殊情况
- 指针和引用:如果初始值是指针或引用,auto会保留它们的类型。
- 数组与函数指针:auto不会直接推断为数组类型或函数类型,而是推断为指针。
#include <iostream>
int main() {
int x = 42;
int& ref = x;
auto a = ref; // a是int类型,而不是int&类型
a = 100;
std::cout << "x: " << x << ", a: " << a << std::endl; // 输出 x: 42, a: 100
return 0;
}
推断返回类型
#include <iostream>
#include <type_traits>
template <typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {
return a + b;
}
int main() {
int x = 5;
double y = 10.5;
// 返回类型被推断为 decltype(x + y),即 double
auto result = add(x, y);
std::cout << "Result: " << result << std::endl; // 输出 15.5
}
C++14引入了一种新特性,即允许在函数返回值处使用auto,编译器会根据函数体中的返回语句自动推断返回类型。这对模板函数尤其有用,可以避免显式地指定返回类型。
#include <iostream>
auto add(int x, int y) {
return x + y; // 编译器推断返回类型为int
}
int main() {
auto result = add(5, 3);
std::cout << "Result: " << result << std::endl; // 输出Result: 8
return 0;
}
C++17引入了结构化绑定,使得可以将多个值的返回结果通过auto绑定到多个变量中。这对于返回std::pair或std::tuple的函数特别有用。
#include <iostream>
#include <tuple>
std::tuple<int, double, std::string> getValues() {
return {42, 3.14, "Hello"};
}
int main() {
auto [x, y, z] = getValues(); // 结构化绑定
std::cout << "x: " << x << ", y: " << y << ", z: " << z << std::endl; // 输出 x: 42, y: 3.14, z: Hello
return 0;
}
decltype 关键字
decltype也是C++11引入的,用于推断表达式的类型,但与auto不同,decltype不会对表达式进行求值,它只会根据表达式的类型来推断变量类型。
特点:
- decltype可以用于任何表达式,推断该表达式的类型。
- 在复杂场景下可以用来获取某个变量或表达式的精确类型。
#include <iostream>
int main() {
int a = 5;
decltype(a) b = a * 2; // 推断b的类型为int
decltype((a)) c = a; // 推断为int&(左值引用)
std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;
int x = 42;
double y = 3.14;
// 使用decltype推断表达式的类型,auto用于变量声明
decltype(x + y) result = x + y; // 推断为double
return 0;
}
模板类型推断
模板类型推断是C++的核心特性之一,允许编译器自动根据传入的模板参数推断类型。
#include <iostream>
template<typename T>
void printType(T x) {
std::cout << "Type: " << typeid(x).name() << ", Value: " << x << std::endl;
}
int main() {
printType(42); // 推断T为int
printType(3.14); // 推断T为double
printType("Hello"); // 推断T为const char*
return 0;
}
在C++11及以后,auto和decltype与模板结合使用,进一步简化了模板编程。
initializer_list
std::initializer_list 允许通过 {} 语法进行列表初始化。它常用于构造函数、函数参数,尤其适合简洁地传递一组值。常见的 C++ 标准容器和自定义类(构造函数接受 std::initializer_list 作为参数的自定义类。
)都可以通过 std::initializer_list 支持列表初始化。
std::initializer_list 的限制:
- 只读性:std::initializer_list 内的元素是常量,不能被修改。如果需要可修改的列表,可以考虑使用其他容器(如 std::vector)。
- 不支持动态大小:std::initializer_list 是不可变的,一旦创建,大小固定。
std::initializer_list 是一个轻量级的类模板,通常由两个指针组成:一个指向元素数组的指针,元素数量的计数。这使得它非常高效,因为它只是持有指向数据的指针,并没有进行额外的拷贝或内存管理。元素可以通过迭代器访问。在编译时能够推导出类型。
template<class T>
class initializer_list {
public:
// 返回指向数组的指针
const T* begin() const noexcept { return _array; }
// 返回数组末尾
const T* end() const noexcept { return _array + _size; }
// 返回元素个数
size_t size() const noexcept { return _size; }
private:
const T* _array;
size_t _size;
};
自定义类使用 std::initializer_list 构造函数
#include <iostream>
#include <initializer_list>
class MyClass {
public:
std::initializer_list<int> values;
MyClass(std::initializer_list<int> list): values(list) {
for (int value : list) {
std::cout << "Initializing with value: " << value << std::endl;
}
}
void printValues() const {
for (int value : values) {
std::cout << value << " ";
}
std::cout << std::endl;
}
};
int main() {
MyClass obj = {5, 10, 15, 20}; // 使用std::initializer_list初始化
obj.printValues(); // 输出 5 10 15 20
return 0;
}
STL
C++中的STL(标准模板库)是编程中非常重要的一个部分,STL不仅提供了丰富的数据结构和算法,同时也是泛型编程的重要应用。
选择合适的STL容器通常取决于你对数据结构的需求,如访问速度、插入/删除操作的频率、内存使用等。以下是一些常见的选择标准:
- std::vector:当你需要频繁随机访问元素,且只在末尾插入/删除时。
- push_back(const T& value):在末尾添加元素。
- pop_back():移除末尾元素。
- size():返回当前元素个数。
- capacity():返回当前分配的内存大小(可以容纳多少元素)。
- reserve(size_t n):预留至少 n 个元素的存储空间。
- resize(size_t n):改变容器大小,增加的部分会默认初始化。
- at(size_t index):访问指定索引处的元素,并进行范围检查。
- operator[]:通过索引访问元素,不进行范围检查。
- front():返回第一个元素。
- back():返回最后一个元素。
- clear():清空所有元素。
- insert(iterator pos, const T& value):在指定位置插入元素。
- erase(iterator pos):移除指定位置的元素。
- emplace_back(args...):原地构造元素并添加到末尾。
- std::deque:一个双端队列,支持在两端进行快速插入和删除。
- push_back(const T& value):在末尾添加元素。
- push_front(const T& value):在开头添加元素。
- pop_back():移除末尾元素。
- pop_front():移除开头元素。
- size():返回当前元素个数。
- at(size_t index):访问指定索引处的元素,并进行范围检查。
- operator[]:通过索引访问元素,不进行范围检查。
- front():返回第一个元素。
- back():返回最后一个元素。
- clear():清空所有元素。
- insert(iterator pos, const T& value):在指定位置插入元素。
- erase(iterator pos):移除指定位置的元素。
- std::array:固定大小数组
- std::forward_list:单向链表
- std::list:双向链表,支持高效的插入和删除操作,尤其是在中间或两端。
- push_back(const T& value):在末尾添加元素。
- push_front(const T& value):在开头添加元素。
- pop_back():移除末尾元素。
- pop_front():移除开头元素。
- size():返回当前元素个数。
- empty():检查容器是否为空。
- front():返回第一个元素。
- back():返回最后一个元素。
- insert(iterator pos, const T& value):在指定位置插入元素。
- erase(iterator pos):移除指定位置的元素。
- remove(const T& value):移除所有等于 value 的元素。
- sort():对列表进行排序。
- reverse():将列表元素顺序反转。
- std::set:是一个不允许重复元素的有序集合,基于红黑树实现。
- insert(const T& value):插入元素,元素会自动排序。
- erase(const T& value):移除指定元素。
- size():返回当前元素个数。
- empty():检查容器是否为空。
- find(const T& value):查找元素,返回指向该元素的迭代器。
- count(const T& value):检查是否包含某个元素(返回 1 或 0)。
- clear():清空所有元素。
- begin():返回指向第一个元素的迭代器。
- end():返回指向最后一个元素之后的迭代器。
- std::unordered_set: 一种基于哈希表实现的容器,用于存储唯一的无序元素。通过哈希表实现常数时间复杂度的插入、删除和查找操作。
- insert(const T& value):向集合中插入元素。如果元素已存在,则不会插入新的值。
- erase(const T& value):从集合中移除等于 value 的元素,若不存在该元素则不做任何操作。
- clear():清空集合中的所有元素。
- find(const T& value):查找元素,返回指向该元素的迭代器,如果未找到则返回 end() 迭代器。
- count(const T& value):检查集合中是否包含元素 value,如果包含则返回 1,否则返回 0。
- size():返回集合中的元素个数。
- empty():检查集合是否为空。
- begin():返回指向第一个元素的迭代器。
- end():返回指向容器末尾的迭代器(最后一个元素的下一个位置)。
- cbegin() 和 cend():返回常量迭代器,保证迭代时集合内容不可修改。
- bucket_count():返回当前哈希表中桶(bucket)的数量。
- max_bucket_count():返回容器可能包含的最大桶数。
- load_factor():返回当前哈希表的装载因子,即元素数量与桶数量的比值。
- rehash(size_t n):重组容器,使其至少有 n 个桶。
- reserve(size_t n):预留足够空间以容纳至少 n 个元素,并减少重哈希操作的次数。
- std::multiset
- std::unordered_multiset
- std::multimap:允许存在重复的键,即相同的键可以关联多个值,一对多的关系。使用 find() 方法查找某个键时,只能返回第一个找到的键值对。如果需要找到该键对应的所有值,可以使用 equal_range() 或者 lower_bound() 和 upper_bound() 组合使用来获取一个范围。
- std::unordered_multimap
- std::map: 是一个基于键值对的有序关联容器,键是唯一的,一对一的关系。
insert(const std::pair<K, V>& value)
:插入键值对。- erase(const K& key):通过键删除元素。
- size():返回当前元素个数。
- empty():检查容器是否为空。
- find(const K& key):查找键值对,返回指向该元素的迭代器。
- count(const K& key):检查是否包含某个键。
- clear():清空所有元素。
- operator[]:通过键访问或插入元素。
- std::unordered_map: 是一个基于哈希表实现的映射,键是唯一的,且不保证顺序。
insert(const std::pair<K, V>& value)
:插入键值对。- erase(const K& key):通过键删除元素。`
- size():返回当前元素个数。
- empty():检查容器是否为空。
- find(const K& key):查找键值对,返回指向该元素的迭代器。
- count(const K& key):检查是否包含某个键。
- clear():清空所有元素。
- operator[]:通过键访问或插入元素。
- std::stack: 适合后进先出 (LIFO) 的数据结构。
- push(const T& value):将元素压入栈顶。
- pop():移除栈顶元素。
- top():返回栈顶元素。
- size():返回当前元素个数。
- empty():检查容器是否为空。
- std::queue: 一种适合先进先出 (FIFO) 的数据结构
- push(const T& value):将元素添加到队尾。
- pop():移除队首元素。
- front():返回队首元素。
- back():返回队尾元素。
- size():返回当前元素个数。
- empty():检查容器是否为空。
- std::priority_queue, 是一种元素具有优先级的队列,默认情况下,最大元素会在队首。
- push(const T& value):将元素插入队列。
- pop():移除优先级最高的元素。
- top():返回优先级最高的元素。
- size():返回当前元素个数。
- empty():检查容器是否为空。
STL容器本身不是线程安全的。多个线程同时读写同一个容器,或者同时修改容器状态(比如插入或删除元素)时,可能会导致数据竞争。因此,需要通过锁(如std::mutex)来确保线程安全。
STL广泛采用了泛型编程,所有STL容器和算法都使用模板,使它们能够处理不同的数据类型。
#include <iostream>
#include <vector>
template <typename T>
void print_vector(const std::vector<T>& vec) {
for (const auto& item : vec) {
std::cout << item << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> int_vec = {1, 2, 3};
std::vector<double> double_vec = {1.1, 2.2, 3.3};
print_vector(int_vec);
print_vector(double_vec);
return 0;
}
RAII
STL中,像std::vector、std::unique_ptr等容器和智能指针都遵循RAII原则。RAII(Resource Acquisition Is Initialization)是一种资源管理的原则。在C++中,RAII意味着对象的生命周期与资源的管理直接相关。当对象被创建时,资源被分配;当对象被销毁时,资源自动释放。
#include <iostream>
#include <vector>
int main() {
{
std::vector<int> vec = {1, 2, 3};
// vec创建时分配内存,RAII管理其生命周期
}
// 离开作用域时,vec自动销毁,内存被释放
return 0;
}
allocator
Allocator是STL中用于管理内存分配的机制。STL中的所有容器都使用allocator来控制如何分配、释放和管理内存。这使得STL能够更灵活地适应不同的内存管理需求。
默认的allocator是std::allocator,但用户可以自定义allocator来优化内存分配策略(比如用于嵌入式系统中的内存池)。
#include <iostream>
#include <vector>
#include <memory>
int main() {
std::vector<int, std::allocator<int>> vec = {1, 2, 3};
return 0;
}
STL中的优先级队列是如何实现的?
STL中的priority_queue是基于堆(heap)实现的。优先级队列默认是一个最大堆,即每次取出的元素都是当前队列中的最大值(或最小值,取决于定义)。堆的性质确保插入和删除的时间复杂度为O(log n)。
代码示例:
#include <iostream>
#include <queue>
#include <vector>
int main() {
std::priority_queue<int> pq;
pq.push(10);
pq.push(5);
pq.push(20);
while (!pq.empty()) {
std::cout << pq.top() << " "; // 每次输出最大值
pq.pop();
}
return 0;
}
这里,std::priority_queue是通过std::vector存储数据,并使用堆来维持顺序。内部使用std::make_heap()、std::push_heap()和std::pop_heap()等算法。
STL中常见的算法库
STL中的算法库是其核心部分之一,包含了很多常见的算法,比如排序、查找、遍历等。以下是一些常见的算法:
- std::sort:快速排序,时间复杂度为O(n log n)。
- std::find:在范围内查找某个元素,线性时间复杂度。
- std::binary_search:二分查找,时间复杂度为O(log n),需要排序数组。
- std::accumulate:用于计算区间内所有元素的累加值。
代码示例:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {1, 3, 5, 2, 4};
// 排序
std::sort(vec.begin(), vec.end());
// 查找
if (std::binary_search(vec.begin(), vec.end(), 3)) {
std::cout << "找到元素3" << std::endl;
}
return 0;
}
map的底层实现
std::map的底层实现是红黑树,这是一种自平衡二叉搜索树。红黑树保证插入、删除、查找的时间复杂度为O(log n)。红黑树通过颜色标记和旋转操作保持平衡。
set的底层实现
std::set的底层实现与std::map类似,也是基于红黑树。区别在于std::set只存储键,而不存储值。
C++ 内存模型(Memory Model)定义了程序中如何对内存进行访问、修改、以及多线程环境下的内存同步规则。它包含了对变量的存储方式、不同存储区的使用、对象的生命周期、以及编译器、硬件对内存的优化策略等多个方面。
内存布局
C++ 程序的内存通常被分为几个区域:
栈区 (Stack):用于局部变量的存储,函数调用时会在栈上为局部变量分配空间,函数返回时自动释放。栈区的内存由系统管理,速度快,但空间有限。
堆区 (Heap):通过动态内存分配(如 new、malloc)创建的对象位于堆区,需要手动管理内存(通过 delete 或 free 释放)。堆的大小比栈大,但操作相对较慢,容易出现内存泄漏。
全局/静态区 (Global/Static Memory):用于存储全局变量、静态变量,它们在程序开始时分配,在程序结束时释放。静态区内的变量在程序运行期间始终存在。
常量区 (Text/Code Segment):存放程序代码、只读数据(如字符串常量)。程序运行期间,这部分内存不可修改。
C++ 对象的生命周期
C++ 中对象的生命周期和内存分配密切相关:
自动存储周期 (Automatic Storage Duration):局部变量存储于栈中,函数调用结束时自动销毁。
静态存储周期 (Static Storage Duration):全局变量和静态局部变量,程序开始时分配,程序结束时销毁。
动态存储周期 (Dynamic Storage Duration):通过 new、malloc 动态分配的内存,在程序显式调用 delete 或 free 之前一直存在。
多线程与内存模型
C++11 引入了明确的内存模型,特别是关于多线程的部分。在多线程编程中,访问共享变量时可能会遇到竞争条件(race condition)。为了解决这些问题,C++ 标准提供了以下机制:
原子操作 (Atomic Operations):C++ 提供了 std::atomic 类模板,用于进行不可分割的原子操作,确保多个线程访问同一个变量时不会产生数据竞争。
内存序
(Memory Ordering)
C++11 引入了内存顺序模型,规定了在多线程环境下不同操作的执行顺序。常见的内存序有:
- 顺序一致性 (Sequential Consistency):所有线程的操作按照程序中的顺序执行,确保全局的执行顺序一致。
- 松散顺序 (Relaxed Ordering):允许编译器和处理器重排序,以提高性能,但可能导致不可预期的执行顺序。
- 获取-释放 (Acquire-Release):用于同步访问,例如在一个线程中获取资源(acquire),并在另一个线程中释放资源(release),确保同步的正确性。
#include <atomic>
#include <thread>
std::atomic<int> x(0), y(0);
void thread1() {
x.store(1, std::memory_order_release);
}
void thread2() {
while (x.load(std::memory_order_acquire) == 0) {}
// 此时可以安全地访问在thread1中x.store之前的所有写入
}
- memory_order_relaxed: 最宽松的内存序,只保证当前操作的原子性。适用于只需要保证操作原子性的场景,如计数器。
- memory_order_consume: 用于读操作,建立了获取-消费(acquire-consume)同步关系。
- memory_order_acquire: 用于读操作,建立了获取(acquire)同步关系。
- memory_order_release: 用于写操作,建立了释放(release)同步关系。(与memory_order_acquire配对使用可以建立同步关系,确保release之前的所有写入对acquire之后的读取可见。)
- memory_order_acq_rel: 同时具有acquire和release语义。
- memory_order_seq_cst: 最严格的内存序,提供全序关系,但可能带来性能开销。
4. C++ 内存模型中的问题
- 内存泄漏 (Memory Leak):程序动态分配的内存没有及时释放,导致内存占用不断增加。
- 悬空指针 (Dangling Pointer):指针指向的内存已经被释放,但指针依旧引用该内存,使用这种指针可能会导致不可预测的行为。
- 内存越界 (Memory Overrun):访问未分配的内存,可能导致程序崩溃或意外的行为。
5. 内存模型优化
编译器和硬件在运行程序时,会进行各种优化(例如缓存、指令重排等)。但这些优化在多线程环境中可能会导致不可预期的结果。为了避免这种情况,C++ 提供了 volatile、内存屏障(memory barriers)等机制来控制优化行为。
volatile 关键字:告诉编译器不要对标记为 volatile 的变量进行优化,因为该变量可能会被外部(如硬件或另一个线程)修改。
内存屏障 (Memory Barriers):内存屏障是一种硬件级的指令,确保在屏障之前的内存操作在屏障之后的操作之前完成。C++11 通过标准库提供了内存屏障的支持。
6. 内存对齐 (Memory Alignment)
C++ 对对象的内存存储有对齐要求,确保每个变量都存储在合适的边界上,以提高内存访问的效率。不同平台对内存对齐的要求不同,编译器可能会自动插入填充字节以满足对齐需求。
通过使用 alignas 和 alignof 关键字,C++11 开始支持手动控制对齐。
浅拷贝与深拷贝
实现深拷贝的时候,需要手动分配以及释放各种动态类型对象,可以考虑使用智能指针来管理内存,减少手动释放内存的风险
学习 Linux 系统能够显著提升工作效率、开发能力和系统管理能力。掌握 Linux 系统的各项技能可以帮助程序员更好地管理和维护开发环境,提升自动化水平、问题解决能力和工作效率。特别是在服务器运维、网络编程、大规模集成开发环境等场景下,Linux 的学习能够显著提升程序员的全方位能力,增加对系统底层的理解,为更复杂的技术栈打下坚实的基础。
1. 基础命令与文件系统管理
重点掌握内容:基本的命令如 ls、cd、cp、mv、rm、chmod、chown、ln、find、grep 等,文件和目录权限(如 rwx 权限的含义),文件路径结构,文件压缩与解压(如 tar、gzip)。 作用:这些命令是管理 Linux 文件系统的基础,掌握它们可以帮助程序员高效地在系统中导航、管理文件、调整权限等。 提升:熟悉这些基础命令可以提高操作系统的工作效率,让程序员更快、更便捷地完成日常开发和运维任务。
2. 文本编辑器
重点掌握内容:使用文本编辑器如 vim、nano、emacs 等进行文件编辑。掌握 vim 的基本操作(如插入模式、命令模式、保存、退出、查找与替换)。
作用:文本编辑器是处理配置文件、代码和脚本的主要工具。快速掌握 vim 等编辑器的使用能够提高工作效率。
提升:熟悉高效的文本编辑工具能够减少切换图形界面编辑器的需求,增强命令行下的开发与配置能力。
3. Shell 脚本编程
重点掌握内容:了解 bash 等 Shell 语言的基础,如变量、条件判断(if)、循环(for、while)、函数、自定义命令,以及管道(|)与重定向(>、>>、<)的使用。
作用:Shell 脚本能够自动化重复性任务,如批量文件处理、自动化部署和系统监控等,是系统运维的必备技能。
提升:通过编写 Shell 脚本,可以大幅减少人工操作,提高工作流程的自动化水平,增强系统运维和批处理能力。
4. 系统权限与用户管理
重点掌握内容:用户和组的管理(如 useradd、usermod、passwd、groupadd 等),文件权限与归属(chmod、chown),sudo 权限配置与管理。
作用:通过掌握这些内容,程序员可以为不同用户分配权限,保护系统安全,防止未经授权的访问。
提升:掌握用户和权限管理能让程序员确保系统的安全性,特别是在服务器或团队协作开发环境中。
5. 进程管理与系统监控
重点掌握内容:使用 ps、top、htop、kill、pkill 等命令管理进程,使用 systemctl 或 service 管理服务,查看系统日志(如 dmesg、journalctl)。
作用:进程管理是系统资源分配和调度的重要环节,了解如何查看和管理进程可以帮助程序员定位性能瓶颈或崩溃原因。
提升:通过掌握进程和系统监控,程序员可以更好地排查系统问题,优化应用性能,了解系统资源的分配与使用。
6. 网络配置与管理
重点掌握内容:了解基本的网络命令如 ifconfig、ping、netstat、ss、traceroute、iptables,学习网络接口配置与调试。
作用:掌握网络相关命令能够帮助程序员配置和管理服务器的网络连接,进行网络调试和诊断,设置防火墙规则。
提升:网络配置能力让程序员能够自己搭建开发或测试环境的网络部分,有助于理解和优化分布式系统或网络应用的性能。
7. 软件包管理
重点掌握内容:使用软件包管理工具如 apt(Ubuntu/Debian 系列)、yum 或 dnf(CentOS/Fedora 系列),安装、更新、卸载软件包,管理软件依赖。 作用:软件包管理是 Linux 系统中安装、升级和管理软件的主要方式,掌握这些工具可以高效管理开发环境。 提升:掌握软件包管理工具,可以让程序员更加自由地定制开发环境,管理系统所需的库和依赖。
8. 日志管理
重点掌握内容:熟悉系统日志文件的路径和格式(如 /var/log 目录中的日志文件),使用 tail、less、grep 等工具查看日志内容,配置日志轮替(如 logrotate)。
作用:日志是系统运行状态的记录,掌握日志查看与分析工具可以帮助程序员定位问题,监控系统运行。
提升:通过熟练掌握日志管理,程序员能够更迅速地排查系统和应用程序的错误,提升问题解决能力。
9. 虚拟化与容器化
重点掌握内容:学习如何使用虚拟机(如 VirtualBox)或容器(如 Docker),掌握虚拟环境的配置和管理。
作用:容器化技术如 Docker 已经成为现代应用开发和部署的核心,掌握这些技术能够提高开发和部署的灵活性与一致性。
提升:通过掌握虚拟化与容器化技术,程序员可以创建独立的开发环境,模拟生产环境,提升开发效率并减少部署出错的概率。
10. 版本控制系统与远程管理
重点掌握内容:学习 git 版本控制系统的使用,掌握基本的 git 操作(如 clone、commit、push、pull、branch),并掌握远程服务器的管理工具如 ssh、scp。
作用:版本控制和远程管理是多人协作开发和维护服务器的重要技能。通过远程连接和管理服务器,程序员可以进行部署、维护和调试。
提升:这些技能对于程序员的协作开发、代码管理和远程运维具有重要意义,能够让程序员更好地参与团队合作和管理生产服务器。
11. 安全与防护
重点掌握内容:掌握基础的安全措施,如使用 SSH 密钥登录、设置防火墙、加固文件权限、配置 Fail2Ban 等防护措施。
作用:提高系统的安全性,防止网络攻击或数据泄露。
提升:安全意识和防护技能是保护系统与数据安全的核心,能够帮助程序员打造更安全可靠的开发和生产环境。
linux中获取cpu信息通过/proc/stat
文件
主要指标
- CPU 使用率:衡量 CPU 的工作负载,通常显示为百分比。可以分为用户态(user space)、系统态(kernel space)和空闲态。
- 负载均值(Load Average):代表一段时间内 CPU 的平均负载,通常会有 1 分钟、5 分钟和 15 分钟的负载均值。
- 进程状态:查看占用 CPU 资源最多的进程。
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef __APPLE__
#include <mach/mach.h> // macOS specific
#endif
// user: 用户态cpu时间
// nice:用户态下具有调整优先级的进程所占用的 CPU 时间
// system: 内核态cpu时间
// idle: 代表 CPU 处于空闲状态时的时间
// iowait: 等待磁盘、网络或其他 I/O 设备完成操作
// irq: 处理硬件中断请求(Interrupt Requests)所占用的 CPU 时间
// softirq: softirq 代表处理软中断(Soft Interrupt)所占用的 CPU
// 时间。软中断是由内核调度,用来处理较低优先级的中断工作,例如网络包处理。
// steal: steal 代表虚拟化环境中,由于虚拟机监控器(hypervisor)将 CPU
// 时间分配给其他虚拟机,当前虚拟机被"抢占"走的时间。
void getCpuUsage() {
#ifdef __linux__
std::ifstream file("/proc/stat");
std::string line;
if (file.is_open()) {
std::getline(file, line); // 读取第一行
std::istringstream iss(line);
std::string cpu;
long user, nice, system, idle, iowait, irq, softirq, steal;
iss >> cpu >> user >> nice >> system >> idle >> iowait >> irq >> softirq >>
steal;
long total_idle = idle + iowait;
long total_non_idle = user + nice + system + irq + softirq + steal;
long total = total_idle + total_non_idle;
std::cout << "CPU Usage (Linux): " << ((total_non_idle * 100.0) / total)
<< "%" << std::endl;
}
file.close();
#elif __APPLE__
// macOS: Use sysctl to get CPU load
host_cpu_load_info_data_t cpuinfo;
mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT;
if (host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO,
(host_info_t)&cpuinfo, &count) == KERN_SUCCESS) {
long total_user = cpuinfo.cpu_ticks[CPU_STATE_USER];
long total_system = cpuinfo.cpu_ticks[CPU_STATE_SYSTEM];
long total_idle = cpuinfo.cpu_ticks[CPU_STATE_IDLE];
long total_nice = cpuinfo.cpu_ticks[CPU_STATE_NICE];
long total = total_user + total_system + total_idle + total_nice;
long total_non_idle = total_user + total_system + total_nice;
std::cout << "CPU Usage (macOS): " << ((total_non_idle * 100.0) / total)
<< "%" << std::endl;
}
#endif
}
int main() {
getCpuUsage();
return 0;
}
命令行工具
- top / htop:实时显示系统运行状态,包括 CPU、内存、负载等。
- 优化分析:可以通过 top 观察 CPU 使用率较高的进程,并针对性地进行优化。htop 是 top 的增强版,提供了更好的界面和交互性。
- mpstat:多核 CPU 的统计数据,显示每个 CPU 核心的使用情况。
- 优化分析:通过查看单核和多核 CPU 的使用情况,可以判断是否需要优化程序的多线程性能。
优化方案
- 进程管理:如果某些进程占用 CPU 资源过多,可以通过 nice、renice 调整进程优先级,或者使用 kill 终止不必要的进程。
- 负载均衡:如果某些任务不需要立刻执行,可以通过 cron 或 at 将任务安排在系统负载较低时运行。
- 多线程优化:对于多核 CPU,可以优化程序的并行执行能力,使得任务分布在不同的 CPU 核心上,充分利用多核 CPU 性能
主要指标
- 总内存:系统中实际的物理内存大小。
- 已用内存:已经被进程占用的内存。
- 空闲内存:尚未使用的物理内存。
- 交换分区(Swap):当物理内存不足时,系统会将不常用的数据移到硬盘上的交换分区。过多使用交换空间会降低系统性能。
- 缓存与缓冲:缓存用于存储文件系统数据,缓冲用于存储即将写入磁盘的数据。
#include <fstream>
#include <iostream>
#include <string>
#include <sys/sysctl.h>
#include <sys/types.h>
#ifdef __APPLE__
#include <mach/mach.h> // macOS specific
#endif
void getMemoryUsage() {
#ifdef __linux__
std::ifstream file("/proc/meminfo");
std::string line;
long total_memory = 0;
long free_memory = 0;
if (file.is_open()) {
while (std::getline(file, line)) {
if (line.find("MemTotal:") == 0) {
sscanf(line.c_str(), "MemTotal: %ld kB", &total_memory);
}
if (line.find("MemFree:") == 0) {
sscanf(line.c_str(), "MemFree: %ld kB", &free_memory);
}
}
file.close();
std::cout << "Total Memory (Linux): " << total_memory / 1024 << " MB"
<< std::endl;
std::cout << "Free Memory (Linux): " << free_memory / 1024 << " MB"
<< std::endl;
std::cout << "Used Memory (Linux): " << (total_memory - free_memory) / 1024
<< " MB" << std::endl;
}
#elif __APPLE__
int64_t total_memory = 0;
int mib[2] = {CTL_HW, HW_MEMSIZE}; // MacOS uses HW_MEMSIZE for total memory
size_t length = sizeof(total_memory);
if (sysctl(mib, 2, &total_memory, &length, NULL, 0) == 0) {
vm_size_t page_size;
mach_port_t mach_port = mach_host_self();
mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
vm_statistics64_data_t vm_stats;
if (host_page_size(mach_port, &page_size) == KERN_SUCCESS &&
host_statistics64(mach_port, HOST_VM_INFO, (host_info64_t)&vm_stats,
&count) == KERN_SUCCESS) {
long long free_memory = (int64_t)vm_stats.free_count * (int64_t)page_size;
long long used_memory = total_memory - free_memory;
std::cout << "Total Memory (macOS): " << total_memory / (1024 * 1024)
<< " MB" << std::endl;
std::cout << "Free Memory (macOS): " << free_memory / (1024 * 1024)
<< " MB" << std::endl;
std::cout << "Used Memory (macOS): " << used_memory / (1024 * 1024)
<< " MB" << std::endl;
}
}
#endif
}
int main() {
getMemoryUsage();
return 0;
}
命令行工具
- free:查看内存使用情况,包括物理内存和交换内存。
- 优化分析:通过 free 可以了解物理内存和交换空间的使用情况,如果交换分区的使用较多,可能需要增加物理内存或优化内存管理。
- vmstat:显示系统的虚拟内存使用、IO 负载、CPU 等。
- 优化分析:通过 vmstat 可以了解内存分配和交换区的使用情况,帮助判断是否需要调整应用程序的内存使用。
- top / htop:显示占用内存最多的进程。
- 优化分析:通过 top 查看内存消耗大户,识别内存泄漏或不必要的内存占用。
优化方案
- 释放缓存:通过执行 echo 3 > /proc/sys/vm/drop_caches 清理缓存,但要谨慎使用,避免影响系统性能。
- 调整交换分区大小:如果物理内存不足且频繁使用交换分区,可以增加交换分区的大小或增加物理内存。
- 优化内存使用:程序优化时应注意防止内存泄漏,确保释放不再使用的内存。
主要指标
- 磁盘空间使用率:每个挂载点的磁盘使用百分比,常用于判断磁盘是否即将耗尽。
- 磁盘 IO 速率:磁盘的读写速率,过高的 IO 可能表示磁盘负载过重。
- I/O 等待(I/O wait):CPU 等待磁盘 I/O 完成的时间,通常表示磁盘访问瓶颈。
#include <iostream>
#include <sys/mount.h> // macOS-specific header for statfs
#include <sys/statvfs.h>
void getDiskUsage(const char *path) {
#ifdef __linux__
struct statvfs stat;
if (statvfs(path, &stat) != 0) {
std::cerr << "Error getting disk usage information (Linux)" << std::endl;
return;
}
unsigned long total = stat.f_blocks * stat.f_frsize;
unsigned long free = stat.f_bfree * stat.f_frsize;
unsigned long available = stat.f_bavail * stat.f_frsize;
unsigned long used = total - free;
std::cout << "Total Disk (Linux): " << total / (1024 * 1024) << " MB"
<< std::endl;
std::cout << "Used Disk (Linux): " << used / (1024 * 1024) << " MB"
<< std::endl;
std::cout << "Available Disk (Linux): " << available / (1024 * 1024) << " MB"
<< std::endl;
#elif __APPLE__
struct statfs stat;
if (statfs(path, &stat) != 0) {
std::cerr << "Error getting disk usage information (macOS)" << std::endl;
return;
}
unsigned long total = stat.f_blocks * stat.f_bsize;
unsigned long free = stat.f_bfree * stat.f_bsize;
unsigned long available = stat.f_bavail * stat.f_bsize;
unsigned long used = total - free;
std::cout << "Total Disk (macOS): " << total / (1024 * 1024) << " MB"
<< std::endl;
std::cout << "Used Disk (macOS): " << used / (1024 * 1024) << " MB"
<< std::endl;
std::cout << "Available Disk (macOS): " << available / (1024 * 1024) << " MB"
<< std::endl;
#endif
}
int main() {
getDiskUsage("/");
return 0;
}
命令行工具
- df:查看文件系统的磁盘使用情况。
- 优化分析:通过 df 查看各个挂载点的磁盘使用率,判断磁盘空间是否充足。
- du:查看指定目录或文件的磁盘使用情况。
- 优化分析:通过 du 可以找到占用磁盘空间较大的文件或目录,方便进行清理。
- iostat:显示磁盘的 I/O 性能,包括读取、写入速率。
- 优化分析:通过 iostat 可以判断磁盘是否存在 I/O 性能瓶颈,发现频繁访问磁盘的进程。
- iotop:类似于 top 的磁盘 I/O 性能监控工具,显示各进程的磁盘 I/O 使用情况。
- 优化分析:通过 iotop 可以发现磁盘 I/O 负载重的进程,并进行优化。
优化方案
- 清理不必要的文件:通过 du 找到大文件,定期清理日志文件、缓存等,保持磁盘空间充足。
- 磁盘分区管理:合理规划磁盘分区,避免单一分区空间过小导致磁盘使用过载。
- 磁盘 I/O 优化:对于 I/O 密集型任务,可以使用 SSD 替代 HDD,或将频繁读写的数据放在更快的磁盘上。
- 文件系统优化:选择合适的文件系统(如 ext4、xfs)和挂载选项来提高磁盘 I/O 性能。
进程与线程的数据结构
1. 进程的数据结构
在Linux内核中,进程(Process)使用 task_struct 数据结构来表示。该结构包含了进程的所有信息,下面列出一些关键字段:
- PID: 进程ID。
- state: 进程当前的状态,比如运行中(TASK_RUNNING)、休眠(TASK_INTERRUPTIBLE)等。
- mm_struct: 进程的内存管理信息,如内存映射、虚拟地址空间等。
- signal_struct: 信号处理相关的信息。
- task_list: 用于将进程链接到进程链表中的指针,形成进程的调度队列。
- sched_class: 调度类,用于定义进程的调度策略,如CFS(Completely Fair Scheduler)。
- thread_info: 保存与线程相关的特定信息,比如栈指针、寄存器状态等。
一个进程的生命周期从 fork() 系统调用开始,创建一个新进程时,父进程会复制自身的某些信息给子进程,例如文件描述符、内存映射等。
2. 线程的数据结构
线程(Thread)在Linux中是轻量级进程,其本质上与进程共享同一地址空间。在Linux中,线程和进程的管理是通过 task_struct 统一管理的,但与进程不同的是,线程共享一部分资源,如地址空间、文件描述符等。
- 共享资源:多个线程之间共享内存、打开的文件、信号处理器等。
- 独立资源:每个线程有自己独立的堆栈和寄存器集。
在Linux中,线程的创建通过 clone() 系统调用,它允许指定新线程共享的资源。
二、进程与线程的创建流程
1. 进程的创建流程
进程的创建通常由 fork() 系统调用完成,其流程如下:
- 父进程调用fork(): 父进程调用 fork() 系统调用。
- 内核分配资源: 内核为新进程分配 task_struct 并复制父进程的所有信息。
- PID分配: 内核为新进程分配一个唯一的进程ID。
- 内存结构复制: 子进程的内存空间是父进程的一个“写时复制”(copy-on-write),即子进程在第一次写操作时才会真正复制父进程的内存区域。
- 调度: 子进程会被加入到调度队列,等待被内核调度运行。
另一个常用的创建进程的系统调用是 exec(),它允许替换进程的地址空间,从而运行新的程序。
2. 线程的创建流程
线程的创建通常使用 pthread_create(),而底层则是使用 clone() 系统调用。线程创建的步骤如下:
- 用户空间调用pthread_create(): 应用程序调用 pthread_create() 来创建一个新线程。
- 内核调用clone(): 内核调用 clone() 系统调用,在创建线程时,可以通过参数指定哪些资源需要共享(如地址空间、文件描述符等)。
- 创建线程控制块: 内核会为新线程分配 task_struct,但新线程与父线程共享内存空间和其他资源。
- 加入调度队列: 新线程被加入到调度队列,等待被调度执行。
三、进程与线程的调度
1. 调度器的概念
Linux内核中的调度器负责决定哪个进程或线程在何时执行。调度器的核心是基于优先级、时间片和调度策略。调度的主要目标是实现多任务处理,提升系统响应速度和吞吐量。
2. 调度策略
Linux的调度器有多种策略,常见的有:
- SCHED_OTHER: 默认的时间共享调度策略,使用CFS(完全公平调度器)。
- SCHED_FIFO: 实时调度策略,基于先入先出,优先级高的进程先调度,且不会被时间片剥夺。
- SCHED_RR: 实时轮转调度,每个进程分配固定的时间片,轮流调度。
- SCHED_IDLE: 低优先级进程,只有在系统空闲时才会被调度。
3. CFS(完全公平调度器)
CFS 是Linux默认的调度器,它通过维护一个红黑树来追踪所有进程的“虚拟运行时间”,虚拟运行时间最短的进程被优先调度。
CFS 主要的机制包括:
- vruntime: 每个进程都有一个 vruntime(虚拟运行时间),表示进程运行的时间。CFS 调度器通过比较 vruntime 来选择最优先的进程。
- 时间片: CFS根据进程的优先级分配时间片,优先级高的进程会获得更多的运行时间。
4. 进程调度流程
- 准备调度: 当进程的时间片耗尽,或有一个优先级更高的进程进入可运行状态时,当前进程会被挂起。
- 选择进程: 调度器遍历任务队列或红黑树,选择下一个要执行的进程。
- 上下文切换: 内核进行上下文切换,保存当前进程的CPU状态,加载新进程的状态,并让新进程获得CPU的控制权。
5. 线程调度流程
线程调度与进程调度在Linux内核中几乎相同。线程也是通过 task_struct 进行管理和调度的,线程会像进程一样根据调度策略和优先级来分配时间片。
6. 多核处理器调度
在多核系统中,Linux内核的调度器支持同时调度多个进程。每个CPU都有一个独立的运行队列,调度器通过负载均衡机制来平衡每个CPU上的任务负载。
负载均衡: 调度器定期检查各个CPU的负载情况,如果发现某个CPU负载过重,会将任务从繁忙的CPU迁移到空闲的CPU上,以实现更均衡的负载分配。
总结来说,Linux的进程和线程在内核中的管理和调度机制高度统一,核心数据结构 task_struct 扮演了关键角色。而调度器则基于调度策略、优先级、时间片等来实现对进程和线程的高效管理。
1. 物理内存 (Physical Memory)
物理内存是计算机系统中的实际硬件存储器,通常指的是随机存取存储器(RAM)。它直接为运行中的程序和操作系统提供存储空间。物理内存的大小是固定的,由硬件决定。操作系统通过一定的策略来管理物理内存的使用。
关键点:
- 物理内存是有限的硬件资源,且不同进程共享这一资源。
- 操作系统使用一定的算法来分配和管理物理内存,确保所有程序有足够的资源运行。
- 由于物理内存有限,操作系统需要虚拟内存技术来拓展其可用性。
2. 虚拟内存 (Virtual Memory)
虚拟内存是一种计算机系统内存管理技术,通过虚拟化物理内存,给每个进程提供一个完整的、连续的内存地址空间。虚拟内存的概念使得程序可以假设自己拥有独立的内存空间,而实际上,它们共享相同的物理内存资源。
操作系统通过将内存划分为页(pages),使用页表将虚拟地址映射到物理地址。 当物理内存不足时,虚拟内存还可以通过将不常用的内存页面暂时存放到硬盘上的交换区(swap space),以实现内存的扩展。 虚拟内存技术让程序可以使用比物理内存更多的内存,并且为每个进程提供一个隔离的内存空间,增强了安全性和稳定性。
关键点:
- 虚拟内存使每个进程看似拥有独立且连续的内存空间。
- 通过分页(paging)或分段(segmentation)机制,虚拟地址可以映射到物理内存地址或磁盘上的交换空间。
3. 用户态内存映射 (User-Space Memory Mapping)
用户态内存映射指的是在用户空间(用户态)中,进程如何通过虚拟地址访问实际的物理内存。通常,用户态程序不直接操作物理内存,而是通过虚拟内存进行访问,操作系统负责将用户态虚拟地址映射到物理地址。
- 用户态的程序不能直接访问内核空间内存,防止破坏操作系统的稳定性和安全性。
- 用户态进程通过系统调用(如 mmap())可以将文件或设备映射到进程的地址空间,这样可以通过内存直接访问文件内容,提高性能。
- 内存映射文件时,操作系统不会立即分配物理内存,而是按需加载到物理内存,节省资源。
关键点:
用户态程序通过虚拟内存间接访问物理内存。
用户态程序可以通过内存映射将文件或设备映射到地址空间,实现更高效的文件读写。
4. 内核态内存映射 (Kernel-Space Memory Mapping)
内核态内存映射指的是操作系统内核在管理系统时如何映射内存。内核态程序(如驱动程序和操作系统自身)拥有直接访问物理内存的权限。内核的职责是为用户态进程管理内存,并为它们提供必要的映射。
- 内核态代码运行在特权级较高的CPU模式下,拥有完全的内存访问权限。
- 内核在用户态进程请求内存时,会为其分配合适的虚拟内存并映射到物理内存,或者将部分内存分页到磁盘。
- 内核内存通常是分页的,但有些关键区域可能会使用大页或者不分页,以提高效率。
关键点:
- 内核态程序直接操作物理内存,负责所有内存的分配和管理。
- 内核态内存映射管理用户态的内存请求,并提供保护和隔离。
5. 进程空间管理 (Process Space Management)
进程空间管理指的是操作系统如何为每个运行中的进程分配和管理它的虚拟内存空间。每个进程都有一个独立的虚拟地址空间,这些虚拟地址通过操作系统映射到物理内存或磁盘中的交换空间。
进程的虚拟地址空间一般分为以下几部分:
- 代码段:存储程序的可执行代码。
- 数据段:存储已初始化的全局变量和静态变量。
- 堆:用于动态分配内存,进程可以在运行时申请内存。
- 栈:用于函数调用时保存局部变量和调用链。
关键点:
- 进程空间管理确保每个进程都有独立的、受保护的内存空间。
- 堆和栈的管理使得程序可以高效利用内存,并在必要时释放或分配资源。
操作系统通过以下机制管理进程空间:
- 分页机制:将进程的虚拟内存划分为固定大小的页面,并将这些页面映射到物理内存或交换空间。
- 分段机制:将虚拟内存划分为段(如代码段、数据段、堆和栈),并进行段内管理。
- 虚拟内存保护:通过不同的权限级别,防止进程互相访问彼此的内存空间,确保安全性和稳定性。
尽管编程语言有所不同,大多数编程语言编写的程序在底层通常会划分成以下基本的内存区域:
代码段(Text Segment)
存储应用程序的可执行指令。 这是只读区域,防止程序修改自身代码。 数据段(Data Segment):
已初始化的全局变量和静态变量存储在此区域。 数据段通常包括已初始化数据段(例如已赋值的全局变量)和未初始化数据段(如未赋值的全局变量,通常称为 BSS 段)。
堆(Heap)
动态内存分配区域,用于在运行时通过 malloc、new 等方式分配的内存。 堆的大小在运行时动态变化,由程序管理内存的分配和释放。
栈(Stack)
存储函数调用时的局部变量、函数参数和返回地址。栈空间是系统自动分配和释放的,大小受限且是连续增长和收缩的。
不同编程语言对内存的管理差异
- C/C++
- 手动内存管理:C 和 C++ 程序员需要显式地管理内存(如通过 malloc/free 或 new/delete)。
- 内存划分经典:堆和栈的分配非常明确,程序员需要直接处理内存泄漏和碎片化等问题。
- 没有垃圾回收:内存管理完全依赖程序员,不存在自动的内存回收机制。
- Java
- JVM 管理内存:Java 程序运行在 Java 虚拟机(JVM)上,内存划分包括堆、栈、方法区(存储类元数据)、本地方法栈和 PC 寄存器。
- 垃圾回收机制:Java 的堆内存由 JVM 管理,并由垃圾回收器自动回收不再使用的对象。
- 栈用于线程:每个线程有自己的栈,存储局部变量和方法调用链。
- Python
- 动态内存分配:Python 所有变量都是对象,所有对象都在堆上分配内存。
- 内存管理由解释器负责:Python 解释器(CPython)会使用引用计数和垃圾回收(GC)机制管理内存。
- 栈和帧:Python 的函数调用栈由帧(frame)构成,帧存储局部变量、参数等。
- Go
- 栈自动扩展:Go 的栈是可以自动扩展的,初始大小很小,但随着需要会动态增长。
- 垃圾回收:Go 有内置的垃圾回收机制,自动管理堆内存。
- 并发友好的内存管理:Go 的内存管理优化了多线程并发性能,特别是 goroutines 的轻量级栈。
- Rust
- 所有权模型:Rust 通过所有权(ownership)、借用(borrowing)和生命周期(lifetimes)来管理内存,避免了手动内存管理,同时避免了垃圾回收的开销。
- 无垃圾回收:Rust 在编译时检查内存使用,确保内存安全,避免传统的垃圾回收机制。
- 栈和堆的明确定义:值默认分配在栈上,只有需要动态分配的对象(如通过 Box、Rc 等)才会在堆上。
在 Linux 系统中,CPU、内存、磁盘和网络等资源的管理和监控是非常重要的,这些资源直接关系到系统性能的高低。要有效分析这些指标,需要理解它们的具体含义,并熟练使用相关命令行工具。以下是各个资源的详细说明、监控工具和优化方案:
4. 网络
主要指标
:
- 带宽使用率:网络接口的上传和下载速率。
- 丢包率:丢失的数据包比例,表示网络通信的稳定性。
- 延迟(Latency):网络通信的响应时间,反映网络的时延。
- 网络连接数:系统的活动连接数,反映了系统的网络负载情况。
命令行工具
:
- ifconfig / ip addr:显示网络接口的配置和状态。
- 优化分析:通过 ifconfig 查看网络接口的运行状态和基本信息,如 IP 地址、带宽、错误包等。
- ping:用于测试网络连通性和延迟。
- 优化分析:通过 ping 测试本地和远程服务器的网络延迟,判断是否存在网络瓶颈。
- netstat / ss:查看系统的网络连接、监听的端口和网络接口的流量统计。
- 优化分析:通过 netstat 可以查看系统上正在使用的端口和连接数,发现异常网络连接或网络瓶颈。
- iftop:实时监控网络接口的流量。
- 优化分析:通过 iftop 可以发现哪些进程或服务占用了过多的带宽,从而针对性地进行优化。
优化方案
:
- 带宽控制:通过 tc(Traffic Control)命令限制某些进程的带宽占用,避免单一进程耗尽网络资源。
- 网络配置优化:优化网络接口的配置,如调整 MTU(最大传输单元)大小、开启 TCP Fast Open 提高网络传输性能。
- 分布式缓存:通过在本地部署缓存服务器(如 Varnish)减少对远程服务器的频繁网络请求,降低带宽使用。
- 网络负载均衡:对于高并发网络应用,可以引入负载均衡器(如 HAProxy),将网络请求分发到多台服务器,降低单台服务器的负载。
ifconfig
- 用于配置和显示网络接口的详细信息,包括 IP 地址、网络掩码、广播地址、MAC 地址等。
- 可以启用或禁用网络接口。
# 查看所有网络接口的配置
ifconfig
# 配置某个接口的 IP 地址
ifconfig eth0 192.168.1.100 netmask 255.255.255.0
# 启用网络接口
ifconfig eth0 up
# 禁用网络接口
ifconfig eth0 down
ip
- ip 命令是功能更强大的网络配置工具,能够配置 IP 地址、路由、网络接口以及网络命名空间等。
- 通过该命令可以显示和修改所有网络配置相关的信息。
# 查看所有网络接口的信息
ip addr show
# 为网络接口添加 IP 地址
ip addr add 192.168.1.100/24 dev eth0
# 删除网络接口的 IP 地址
ip addr del 192.168.1.100/24 dev eth0
# 启用网络接口
ip link set eth0 up
# 禁用网络接口
ip link set eth0 down
# 查看路由表
ip route show
# 添加默认网关
ip route add default via 192.168.1.1
ping
- ping 用于测试主机之间的连通性,通过向目标主机发送 ICMP 请求并接收响应来测试网络连接状况。
- 可以用来测量网络的延迟(RTT,Round Trip Time)。
# 测试与远程主机的连通性
ping www.google.com
# 指定发送数据包的数量(4 个)
ping -c 4 www.google.com
netstat/ss
- netstat 命令用于显示网络连接、路由表、接口统计信息以及多播成员。
- ss 是 netstat 的替代工具,用于显示更详细的套接字(socket)信息,速度比 netstat 快。
# 显示所有当前的网络连接
netstat -an
# 查看系统中所有监听的端口
netstat -tuln
# 使用 ss 显示监听的端口
ss -tuln
# 显示详细的 TCP 连接信息
ss -ta
traceroute
- traceroute 用于显示从本地主机到目标主机之间经过的路由节点(跳数),帮助定位网络故障点或延迟来源。
- 它通过向目的地发送 TTL 递增的 ICMP 数据包,并逐步显示路径中的各个路由节点。
# 显示到目标主机的路由路径
traceroute www.google.com
# 使用 ICMP 进行路由追踪
traceroute -I www.google.com
nslookup/dig
- nslookup 和 dig 是 DNS 查询工具,用于查询域名解析信息。
- nslookup 是较老的工具,dig 提供更多详细信息并且功能更强大。
# 使用 nslookup 查询域名的 IP 地址
nslookup www.google.com
# 使用 dig 查询域名的详细 DNS 解析记录
dig www.google.com
# 反向查询 IP 对应的域名
dig -x 8.8.8.8
route
- route 命令用于查看和配置系统的路由表。
- 主要用于管理网络流量的路由路径,例如添加、删除或显示网关的路由规则。
# 显示当前的路由表
route -n
# 添加默认网关
route add default gw 192.168.1.1
# 删除路由
route del -net 192.168.1.0/24 gw 192.168.1.1
ethtool
- ethtool 命令用于查询和修改网卡(Ethernet device)的配置和状态,支持硬件属性的查看与设置。
- 查看和修改网络接口的硬件参数,如速率、双工模式、自协商模式等。
- 排查网络接口性能问题,或调整网卡参数以优化网络性能。
# 显示网卡 eth0 的硬件信息
ethtool eth0
# 禁用网卡的自协商功能
ethtool -s eth0 autoneg off
# 设置网卡速率为 100Mbps,全双工模式
ethtool -s eth0 speed 100 duplex full
iptables/nftables
- iptables 是 Linux 系统的防火墙工具,用于配置网络包过滤规则,管理入站和出站的网络流量。
- nftables 是 iptables 的替代工具,功能更强大且更灵活,推荐在新系统中使用。
- 配置防火墙规则,限制或允许某些端口的访问。
- 设置 NAT(网络地址转换)规则,实现路由功能。
# 列出当前的 iptables 规则
iptables -L
# 允许特定端口的流量(如 22 端口的 SSH 连接)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# 删除规则
iptables -D INPUT -p tcp --dport 22 -j ACCEPT
# 清空所有规则
iptables -F
nmcli
nmcli 是 NetworkManager 的命令行接口,用于配置网络连接、管理网络设备和显示网络状态。
# 查看当前的网络连接
nmcli con show
# 启用网络连接
nmcli con up id "Wired connection 1"
# 禁用网络连接
nmcli con down id "Wired connection 1"
# 创建新的有线连接
nmcli con add type ethernet ifname eth0