【LeetCode】数组精选17题——双指针、滑动窗口、前缀和

news2024/11/28 12:44:17

目录

快慢指针:

1. 移动零(简单)

2. 复写零(简单)

对撞指针:

1. 两数之和 II - 输入有序数组(中等)

2. 三数之和(中等)

3. 有效三角形的个数(中等)

4. 四数之和(中等)

5. 盛最多水的容器(中等)

滑动窗口:

1. 长度最小的子数组(中等)

2. 将 x 减到 0 的最小操作数(中等)

3. 乘积小于 K 的子数组(中等)

4. 最大连续1的个数 III(中等)

5. 水果成篮(中等)

前缀和:

1. 寻找数组的中心下标(简单)

2. 和为 K 的子数组(中等)

3. 连续数组(中等)

4. 二维区域和检索 - 矩阵不可变(中等)

5. 矩阵区域和(中等)


双指针是一种常用的算法技巧,通常用于线性数据结构(数组、字符串、链表),使用两个指针扫描。指针并不是指C语言中的指针,而是指能定位数据结构中某个数据的手段,在数组中指的是下标。

双指针的形式:

  • 快慢指针:两个指针从一端向另一端移动,但移动速度不同
  • 对撞指针:两个指针从两端向中间移动,直到相遇或错开

滑动窗口是一种特殊的双指针,两个指针构造一个窗口,用于解决子数组的问题。右边界右移为进窗口,左边界右移为出窗口。

正数数组中子数组的和或积,就可以用滑动窗口解决。如果数组元素不全为正数,求子数组的和不能用滑动窗口,因为当数组中有负数时在子数组中添加数字不一定能增加数组之和,从子数组中删除数字也不一定能减少子数组之和。这时就需要用到前缀和思想。

快慢指针:

1. 移动零(简单)

数组分块问题,即根据一种划分方式,将数组分成两块,可以使用双指针解决。

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int n = nums.size();
        int slow = -1; // 指向非0元素的最后一个
        int fast = 0;  // 指向待处理元素的第一个

        while (fast < n)
        {
            if (nums[fast] == 0) // fast扫描到0
            {
                fast++;
            }
            else // fast扫描到非0
            {
                swap(nums[++slow], nums[fast]);
                fast++;
            }
        }
    }
};

2. 复写零(简单)

如果从前往后原地复写0的话,会导致后面的元素被覆盖。例如,[2,0,5,1]    ->    [2,0,0,1]

所以本题采用从后往前复写0,首先要找到原数组从哪里截断。

当fast = n - 1时,说明数组给待处理元素剩的位置只有1个了,这个位置就是留给slow的,不管arr[slow]是0还是非0。将arr[slow]填到数组最后一个位置,然后从slow - 1开始从后往前复写。

当fast = n时,说明数组给待处理元素已经不剩位置了,并且fast一定是从n - 2位置走两步到n,所以上一步的slow一定指向0,然后写了2个0,到达数组末尾。将0填到数组最后一个和倒数第二个位置,然后从slow-2开始从后往前复写。

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        int n = arr.size();
        int slow = 0;
        int fast = 0;
        // 找到截断点
        while (fast < n - 1)
        {
            // slow指向待处理元素的第一个
            // fast表示,如果发生复写,待处理元素的第一个的真正的位置
            if (arr[slow])
            {
                fast++;
            }
            else
            {
                fast +=2;
            }
            slow++;
        }
        // 判断fast,然后让slow指向待处理元素的第一个,fast表示待处理位置的第一个
        if (fast == n - 1)
        {
            arr[n - 1] = arr[slow];
            slow--;
            fast--;
        }
        else
        {
            arr[n - 1] = arr[n - 2] = 0;
            slow -= 2;
            fast -= 3;
        }
        // 从后往前复写
        while (slow >= 0)
        {
            if (arr[slow])
            {
                arr[fast] = arr[slow];
                fast--;
            }
            else
            {
                arr[fast] = arr[fast - 1] = 0;
                fast -= 2;
            }
            slow--;
        }
    }
};

对撞指针:

1. 两数之和 II - 输入有序数组(中等)

对撞指针的应用之一就是求排序数组的两个数字之和。如果和 < 目标值,左指针++,如果和 > 目标值,右指针--,直到和 == 目标值,就找到了那两个数。

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int n = numbers.size();
        int left = 0;
        int right = n - 1;
        while (left < right)
        {
            if (numbers[left] + numbers[right] < target)
            {
                left++;
            }
            else if (numbers[left] + numbers[right] > target)
            {
                right--;
            }
            else
            {
                return { ++left,++right }; // 因为下标从1开始
            }
        }
        return { -1,-1 };
    }
};

2. 三数之和(中等)

和两数之和类似,如果是排序数组,那么固定nums[i],用对撞指针从i的后面找nums[left] + nums[right] == -nums[i]。

答案不能有重复,所以要在找到一种答案后,跳过重复的元素。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(), nums.end());
        vector<vector<int>> ans;
        
        for (int i = 0; i < n - 2; )
        {
            int left = i + 1;
            int right = n - 1;
            while (left < right)
            {
                if (nums[left] + nums[right] < -nums[i])
                {
                    left++;
                }
                else if (nums[left] + nums[right] > -nums[i])
                {
                    right--;
                }
                else
                {
                    ans.push_back({ nums[i],nums[left++],nums[right--] });
                    // 去重left
                    while (left < right && nums[left] == nums[left - 1])
                    {
                        left++;
                    }
                    // 去重right
                    while (left < right && nums[right] == nums[right + 1])
                    {
                        right--;
                    }
                }
            }
            i++;
            // 去重i
            while (i < n - 2 && nums[i] == nums[i - 1])
            {
                i++;
            }
        }
        return ans;
    }
};

3. 有效三角形的个数(中等)

三角形的判定:任意两边之和大于第三边。

只要两条较短边>最长边,就可以推出任意两边之和大于第三边。

和三数之和类似,先给数组排序,固定最长边nums[i],用对撞指针从i的前面找nums[left] + nums[right] > nums[i]。

一旦向右移动left到某个位置时nums[left] + nums[right] > nums[i],就不需要再向右移动left了。因为只要保持right不动,[left, right)这个下标区间对应的任意一个元素,加上nums[right],都一定比nums[i]大。此时两个指针之间有多少个数字,就找到了多少组两条较短边>最长边。

答案可以有重复,不用跳过重复的元素。

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(), nums.end());
        int ans = 0;

        for (int i = n - 1; i >= 2; i--)
        {
            int left = 0;
            int right = i - 1;
            while (left < right)
            {
                if (nums[left] + nums[right] > nums[i])
                {
                    ans += right - left;
                    right--;
                }
                else
                {
                    left++;
                }
            }
        }
        return ans;
    }
};

4. 四数之和(中等)

先排序,固定一个数nums[a],再利用三数之和的解法找到三个数nums[b] nums[c] nums[d],使

nums[b] + nums[c] + nums[d] == target - nums[a]。

答案不能有重复,所以要在找到一种答案后,跳过重复的元素。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        int n = nums.size();
        sort(nums.begin(), nums.end());
        vector<vector<int>> ans;
        
        for (int i = 0; i < n - 3; ) // 固定nums[i]
        {
            for (int j = i + 1; j < n - 2; ) // 固定nums[j]
            {
                int sum1 = nums[i] + nums[j];
                int left = j + 1;
                int right = n - 1;
                while (left < right)
                {
                    int sum2 = nums[left] + nums[right];
                    if (sum2 < (long long)target - sum1)
                    {
                        left++;
                    }
                    else if (sum2 > (long long)target - sum1)
                    {
                        right--;
                    }
                    else
                    {
                        ans.push_back({ nums[i],nums[j],nums[left++],nums[right--] });
                        // 去重left
                        while (left < right && nums[left] == nums[left - 1])
                        {
                            left++;
                        }
                        // 去重right
                        while (left < right && nums[right] == nums[right + 1])
                        {
                            right--;
                        }
                    }
                }
                j++;
                // 去重j
                while (j < n - 2 && nums[j] == nums[j - 1])
                {
                    j++;
                }
            }
            i++;
            // 去重i
            while (i < n - 3 && nums[i] == nums[i - 1])
            {
                i++;
            }
        }
        return ans;
    }
};

5. 盛最多水的容器(中等)

假设左板的下标是left,右板的下标是right,两板与x轴构成的矩形的面积为:

S = (right - left) * min(height[left], height[right])

显然,矩形的面积由底边宽度和短板长度共同决定。

如果改变短板,向内收窄一格,底边宽度-1,短板的长度可能会变大,矩形的面积可能变大。

如果改变长板,向内收窄一格,底边宽度-1,由于短板决定高度,长板再怎么改变也没有用,矩形的面积一定变小。

class Solution {
public:
    int maxArea(vector<int>& height) {
        int n = height.size();
        int left = 0;
        int right = n - 1;
        int ans = 0;
        while (left < right)
        {
            ans = max(ans, ((right - left) * min(height[left], height[right])));
            if (height[left] < height[right])
            {
                left++;
            }
            else
            {
                right--;
            }
        }
        return ans;
    }
};

滑动窗口:

1. 长度最小的子数组(中等)

进窗口后判断子数组的和是否>= target,如果找到了和>= target的子数组,更新结果,然后出窗口,缩小子数组的长度,直到子数组的和 < target。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size();
        int left = 0;
        int right = 0;
        int sum = 0;
        int ans = INT_MAX;
        while (right < n)
        {
            // 进窗口
            sum += nums[right];
            // 判断子数组的和是否>=target
            while (sum >= target && left <= right)
            {
                // 更新结果
                ans = min(ans, right - left + 1);
                // 出窗口
                sum -= nums[left++];
            }
            // 更新右边界
            right++;
        }
        return ans == INT_MAX ? 0 : ans;
    }
};

2. 将 x 减到 0 的最小操作数(中等)

问题可以理解成:求和为x的最左端子数组和最右端子数组的最小长度。

把问题转化成:求和为total - x的子数组的最大长度(total为数组所有元素的和)。

这道题nums[i]的取值范围是:1 <= nums[i] <= 104,求正数数组中子数组的和,显然用滑动窗口。

进窗口后判断子数组的和是否> total - x,如果已经> total - x了,要出窗口,减小子数组的和,直<= total - x。然后判断子数组的和到底是不是== total - x,如果找到了和== total - x的子数组,更新结果。

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int n = nums.size();
        int total = 0;
        for (auto& e : nums)
        {
            total += e;
        }

        int left = 0;
        int right = 0;
        int sum = 0;
        int ans = -1;
        while (right < n)
        {
            // 进窗口
            sum += nums[right];
            // 判断子数组的和是否>total-x
            while (sum > total - x && left <= right)
            {
                // 出窗口
                sum -= nums[left++];
            }
            // 找到和为total-x的子数组,更新结果
            if (sum == total - x)
            {
                ans = max(ans, right - left + 1);
            }
            // 更新右边界
            right++;
        }
        return ans == -1 ? ans : n - ans;
    }
};

3. 乘积小于 K 的子数组(中等)

进窗口后判断子数组的积是否>= k,如果已经 >= k了,要出窗口,减小子数组的积,直到< k。这时得到的子数组就是满足条件的子数组,并且,只要保持右边界不动,向右移动左边界形成的所有子数组的积就一定< k。此时左右边界之间有多少个元素,就找到了多少个乘积< k的子数组。

class Solution {
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) {
        int n = nums.size();
        int left = 0;
        int right = 0;
        int prod = 1;
        int ans = 0;
        while (right < n)
        {
            // 进窗口
            prod *= nums[right];
            // 判断子数组的积是否>=k
            while (prod >= k && left <= right)
            {
                // 出窗口
                prod /= nums[left++];
            }
            // 更新结果
            ans +=  right - left + 1;
            // 更新右边界
            right++;
        }
        return ans;
    }
};

4. 最大连续1的个数 III(中等)

把问题转化成:求0的个数不超过k个的最长子数组。

用zero记录窗口中0的个数。进窗口的同时要维护zero,然后判断zero是否> k,如果已经> k了,要出窗口并维护zero,直到<= k。这时就找到了0的个数不超过k个的子数组,更新结果。

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int n = nums.size();
        int left = 0;
        int right = 0;
        int zero = 0; // 窗口中0的个数
        int ans = 0;
        while (right < n)
        {
            // 进窗口+维护zero
            if (nums[right] == 0)
                zero++;
            // 判断zero是否>k
            while (zero > k && left <= right)
            {
                // 出窗口+维护zero
                if (nums[left] == 0)
                {
                    zero--;
                }
                left++;
            }
            // 更新结果
            ans = max(ans, right - left + 1);
            // 更新右边界
            right++;
        }
        return ans;
    }
};

5. 水果成篮(中等)

把问题转化成:求元素的种类只有2种的最长子数组。

用哈希表记录窗口中元素的种类和次数,key——元素的种类,value——每个元素出现的次数,哈希表的大小表示有多少种元素在窗口中。

进窗口后判断哈希表的大小是否> 2,如果已经> 2了,要出窗口,记得要把value为0的键值对删除,直到哈希表的大小== 2。这时就找到了元素的种类只有2种的子数组,更新结果。

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int n = fruits.size();
        int left = 0;
        int right = 0;
        unordered_map<int, int> hash; // key——元素的种类,value——每个元素出现的次数
        int ans = 0;
        while (right < n)
        {
            // 进窗口
            hash[fruits[right]]++;
            // 判断哈希表的大小是否>2
            while (hash.size() > 2 && left <= right)
            {
                // 出窗口
                hash[fruits[left]]--;
                if (hash[fruits[left]] == 0)
                {
                    hash.erase(fruits[left]);
                }
                left++;
            }
            // 更新结果
            ans = max(ans, right - left + 1);
            // 更新右边界
            right++;
        }
        return ans;
    }
};

前缀和:

1. 寻找数组的中心下标(简单)

如果sum - nums[i] == total - sum,i就是中心下标。

class Solution {
public:
    int pivotIndex(vector<int>& nums) {
        int n = nums.size();
        int total = 0; // 所有元素的和
        for (auto& i : nums)
        {
            total += i;
        }
        int sum = 0; // 表示左子数组的和+nums[i]
        for (int i = 0; i < n; i++)
        {
            sum += nums[i];
            if (sum - nums[i] == total - sum)
                return i;
        }
        return -1;
    }
};

2. 和为 K 的子数组(中等)

每次累加新的前缀和,都往前找找有没有以前的前缀和 == 新的前缀和 - k。

用哈希表记录前缀和出现的次数,默认添加一个键值对(0, 1)。用sum表示当前位置的前缀和。

例如,nums = [3,2,1],k = 3,

将nums[0]添加进sum,sum = 3,sum - k = 0,恰好哈希表中存放了值为0的前缀和,出现的次数为1,将出现的次数添加进ans中,ans = 1。然后把当前位置的前缀和在哈希表中对应的值++,此时哈希表中有2个键值对:(0, 1), (3, 1)。

将nums[1]添加进sum,sum = 5,sum - k = 2,哈希表中没有存放值为2的前缀和,没找到满足条件的子数组,对ans不做处理。然后把当前位置的前缀和在哈希表中对应的值++,此时哈希表中有3个键值对:(0, 1), (3, 1), (5, 1)。

将nums[2]添加进sum,sum = 6,sum - k = 3,恰好哈希表中存放了值为3的前缀和,出现的次数为1,将出现的次数添加进ans中,ans = 2。然后把当前位置的前缀和在哈希表中对应的值++,此时哈希表中有4个键值对:(0, 1), (3, 1), (5, 1), (6, 1)。

循环结束,输出ans。

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {      
        int n = nums.size();
        unordered_map<int, int> hash; // key——前缀和 value——出现的次数
        hash[0] = 1; // 添加(0, 1)
        int sum = 0; // 当前位置的前缀和
        int ans = 0;
        for (auto& i : nums)
        {
            sum += i;
            if (hash.count(sum - k))
            {
                ans += hash[sum - k];
            }
            hash[sum]++;
        }
        return ans;
    }
};

3. 连续数组(中等)

把数组所有0替换成-1,题目转变为:找到和为0的最长连续子数组。

每次累加新的前缀和,都往前找找有没有以前的前缀和 == 新的前缀和。

用哈希表记录前缀和对应的最小下标(因为求最大子数组),默认添加一个键值对(0, -1)。用sum表示当前位置的前缀和。

例如,nums = [0,1,0],

将nums[0]添加进sum,如果是0,添加-1,如果是1,添加1,sum = -1,哈希表中没有存放值为-1的前缀和,没找到满足条件的子数组,对ans不做处理。然后把当前位置的前缀和及下标添加进哈希表中,此时哈希表中有2个键值对:(0, -1), (-1, 0)。

将nums[1]添加进sum,如果是0,添加-1,如果是1,添加1,sum = 0,恰好哈希表中存放了值为0的前缀和,下标为-1,所以子数组长度为1 - (-1) = 2,将它和ans比较,取最大的,ans = 2。

将nums[2]添加进sum,如果是0,添加-1,如果是1,添加1,sum = -1,恰好哈希表中存放了值为-1的前缀和,下标为0,所以子数组长度为2 - 0 = 2,将它和ans比较,取最大的,ans = 2。

循环结束,输出ans。

class Solution {
public:
    int findMaxLength(vector<int>& nums) {
        int n = nums.size();
        unordered_map<int, int> hash; // key——前缀和 value——最小下标
        hash[0] = -1; // 添加(0, -1)
        int sum = 0; // 当前位置的前缀和
        int ans = 0;
        for (int i = 0; i < n; i++)
        {
            sum += nums[i] == 0 ? -1 : 1;
            if (hash.count(sum))
            {
                ans = max(ans, i - hash[sum]);
            }
            else
            {
                hash[sum] = i;
            }
        }
        return ans;
    }
};

4. 二维区域和检索 - 矩阵不可变(中等)

创建一个和matrix一样大的二维前缀和数组sums,sums[i][j]记录matrix从(0,0)到(i,j)的子矩阵数字和。

(r1,c1)->(r2,c2)子矩阵数字和 =

sums[r2][c2] - sums[r2][c1 - 1] - sums[r1 - 1][c2] + sums[r1 - 1][c1 - 1]

如果r1或c1为0,r1 - 1或c1 - 1会越界。

为了防止越界,在sums的上面和左面分别加一行0和一列0,这样的话,sums[i+1][j+1]记录matrix从(0,0)到(i,j)的子矩阵数字和。

所以,(r1,c1)->(r2,c2)子矩阵数字和 =

sums[r2 + 1][c2 + 1] - sums[r2 + 1][c1] - sums[r1][c2 + 1] + sums[r1][c1]

class NumMatrix {
public:
    public:
    NumMatrix(vector<vector<int>>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();
        sums.resize(m + 1, vector<int>(n + 1, 0)); // 上面和左面分别加一行0和一列0,防止越界
        // 处理前缀和数组
        for (int i = 0; i < m; i++)
        {
            int rowSum = 0;
            for (int j = 0; j < n; j++)
            {
                rowSum += matrix[i][j];
                sums[i + 1][j + 1] = sums[i][j + 1] + rowSum;
            }
        }
    }
    
    int sumRegion(int row1, int col1, int row2, int col2) {
        return sums[row2 + 1][col2 + 1] - sums[row2 + 1][col1] - sums[row1][col2 + 1] + sums[row1][col1];
    }

private:
    vector<vector<int>> sums; // sums[i+1][j+1]记录matrix从(0,0)到(i,j)的子矩阵数字和
};

5. 矩阵区域和(中等)

和上一题“二维矩阵区域和检索 - 矩阵不可变”类似。

注意矩阵左上角和右下角坐标的确定:

  • 左上角:r1 = max(0, i - k)          c1 = max(0, j - k)
  • 右下角:r2 = min(m - 1, i + k)    c2 = min(n - 1, j + k)
class Solution {
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
        int m = mat.size();
        int n = mat[0].size();

        // 处理前缀和矩阵
        vector<vector<int>> sums(m + 1, vector<int>(n + 1, 0)); // sums[i+1][j+1]记录matrix从(0,0)到(i,j)的子矩阵数字和
        for (int i = 0; i < m; i++)
        {
            int rowSum = 0;
            for (int j = 0; j < n; j++)
            {
                rowSum += mat[i][j];
                sums[i + 1][j + 1] = sums[i][j + 1] + rowSum;
            }
        }

        // 处理ans矩阵
        vector<vector<int>> ans(m, vector<int>(n));
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {
                int r1 = max(0, i - k);
                int c1 = max(0, j - k);
                int r2 = min(m - 1, i + k);
                int c2 = min(n - 1, j + k);
                ans[i][j] = sums[r2 + 1][c2 + 1] - sums[r2 + 1][c1] - sums[r1][c2 + 1] + sums[r1][c1];
            }
        }
        return ans;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1318574.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【员工工资册】————大一期末答辩近满分作业分享

前言 大家好吖&#xff0c;欢迎来到 YY 滴项目系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C语言的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; PS&#xff1a;以下内容是部分展示&am…

全球汽车行业的数字化转型:产品和后端的渐进之旅

如何管理汽车行业的数字化转型?在我们本篇文章中了解更多有关如何设定长期目标的信息。 正在改变汽车行业的26个数字化主题 最近一篇关于汽车行业数字化转型的论文确定了26个数字技术主题&#xff08;论文详情请点击阅读原文&#xff09;&#xff0c;分为三个主要集群: 1)驾驶…

RabbitMQ手动应答与持久化

1.SleepUtil线程睡眠工具类 package com.hong.utils;/*** Description: 线程睡眠工具类* Author: hong* Date: 2023-12-16 23:10* Version: 1.0**/ public class SleepUtil {public static void sleep(int second) {try {Thread.sleep(1000*second);} catch (InterruptedExcep…

深入理解Java关键字volatile

前置知识-了解以下CPU结构 如下图所示&#xff0c;每个CPU都会有自己的一二级缓存&#xff0c;其中一级缓存分为数据缓存和指令缓存&#xff0c;这些缓存的数据都是从内存中读取的&#xff0c;而且每次都会加载一个cache line&#xff0c;关于cache line的大小可以使用命令cat…

DOS 系统(命令行)

文章目录 DOS 系统DOS 常用命令DOS 高级命令DOS 批处理命令DOS 应用场景 DOS 系统 操作系统的发展史&#xff08;DOS/Windows篇&#xff09; DOS操作系统的历史 DOS&#xff08;Disk Operating System&#xff09; 是 磁盘操作系统 的缩写&#xff0c;是一种早期的个人计算机操…

Mybatis的插件运⾏原理,如何编写⼀个插件?

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

Linux:TCP 序列号简介

文章目录 1. 前言2. 什么是 TCP 序列号&#xff1f;3. TCP 序号 的 初始值设置 和 后续变化过程3.1 三次握手 连接建立 期间 客户端 和 服务端 序号 的 变化过程3.1.1 客户端 socket 初始序号 的 建立3.1.2 服务端 socket 初始序号 的 建立3.1.3 客户端 socket 接收 服务端 SAC…

动态规划优化技巧

一、斐波那契系列 1、滚动数组优化空间复杂度 2、dp数组初始化/处理边界优化 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台

《Global illumination with radiance regression functions》

总结一下最近看的这篇结合神经网络的全局光照论文 这是一篇2013年TOG的论文。 介绍 论文的主要思想是利用了神经网络的非线性特性去拟合全局光照中的间接光照部分&#xff0c;采用了基础的2层MLP去训练&#xff0c;最终能实现一些点光源、glossy材质的光照渲染。为了更好的理…

【POI的如何做大文件的写入】

&#x1f513;POI如何做大文件的写入 &#x1f3c6;文件和POI之间的区别是什么&#xff1f;&#x1f3c6;POI对于当今的社会发展有何重要性&#xff1f;&#x1f3c6;POI大文件的写入&#x1f396;️使用XSSF写入文件&#x1f396;️使用SXSSFWorkbook写入文件&#x1f396;️对…

《ThreadLocal使用与学习总结:2023-12-15》由浅入深全面解析ThreadLocal

由浅入深全面解析ThreadLocal 目录 由浅入深全面解析ThreadLocal简介基本使用ThreadLocal与synchronized的区别ThreadLocal现在的设计&#xff08;JDK1.8&#xff09;ThreadLocal核心方法源码分析ThreadLocalMap源码分析弱引用与内存泄露&#xff08;内存泄漏和弱引用没有直接关…

代码随想录算法训练营第十四天 | 二叉树理论基础、递归遍历 、迭代遍历、统一迭代

今天学习内容&#xff1a;二叉树理论基础、递归遍历 、迭代遍历、统一迭代 讲解&#xff1a;代码随想录 二叉树题目思维导图如下&#xff0c;来自代码随想录。 1.二叉树理论基础 1.1二叉树种类 满二叉树 完全二叉树 二叉搜索树 平衡二叉搜索树 C中map、set、multimap&…

[Verilog] Verilog 操作符与表达式

主页&#xff1a; 元存储博客 文章目录 前言1. 操作符2. 操作数3 表达式总结 前言 1. 操作符 图片来源&#xff1a; https://www.runoob.com/ Verilog语言中使用的操作符包括&#xff1a; 算术操作符&#xff1a;加法()、减法(-)、乘法(*)、除法(/)、取模(%)、自增()、自减(–…

Vue中插槽的使用

目录 一、默认插槽 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;代码展示 &#xff08;3&#xff09;后备内容 二、具名插槽 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;代码展示 三、作用域插槽 &#xff08;1&#xff09;概念 &#xff0…

【经典LeetCode算法题目专栏分类】【第2期】组合与排列问题系列

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 组合总和1 class So…

【计算机组成原理】存储系统基本概念与基本组成

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

FRP内网映射家用服务器至公网访问

兄弟们&#xff0c;服务器到货了&#xff0c;后续与大家分享内容就用它了。我预装的操作系统是Centos8,首先要解决的是远程访问的问题。 【特别注意】下述的端口&#xff0c;记得在阿里云安全组配置中放开端口入规则&#xff01;&#xff01; 1. FRP服务器配置 之前我有购买…

UDP多人聊天室

讲解的是TCP和UDP两种通信方式它们都有着自己的优点和缺点 这两种通讯方式不通的地方就是TCP是一对一通信 UDP是一对多的通信方式 UDP通信 主要的方向是一对多通信方式 UDP通信就是一下子可以通信多个对象&#xff0c;这就是UDP对比TCP的优势&#xff0c;UDP它的原理呢 就是…

Spring之容器:IOC(1)

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

破译模式:模式识别在计算机视觉中的作用

一、介绍 在当代数字领域&#xff0c;计算机视觉中的模式识别是关键的基石&#xff0c;推动着众多技术进步和应用。本文探讨了计算机视觉中模式识别的本质、方法、应用、挑战和未来趋势。通过使机器能够识别和解释视觉数据中的模式&#xff0c;模式识别不仅推动了计算机视觉领域…