返回

数据结构和算法刷题总结(持续更新)

发布时间:2022-11-19 06:50:08 302
# less# c++# git# 数据# 信息

常见算法总结

1. 排序算法

1.1 选择排序

时间复杂度:O(n^2) 空间复杂度:O(1) 稳定性:不稳定

void SelectSort(vector& nums)
{
for (int i = 0; i < nums.size(); ++i)
{
int minIndex = i;
for (int j = i + 1; j < nums.size(); ++j)
minIndex = (nums[minIndex] > nums[j]) ? j : minIndex;
::swap(nums[minIndex], nums[i]);
}
}

1.2 冒泡排序

时间复杂度:O(n^2) 空间复杂度:O(1) 稳定性:稳定

void BubleSort(vector& nums)
{
for (int i = nums.size() - 1; i > 0; --i)
{
for (int j = 0; j < i; ++j)
{
if (nums[j] > nums[j + 1])
::swap(nums[j], nums[j + 1]);
}
}
}

1.3 插入排序

时间复杂度:O(n^2) 空间复杂度:O(1) 稳定性:稳定

void InsertSort(vector& nums)
{
if (nums.size() <= 1)
return;
for (int i = 1; i < nums.size(); ++i)
{
for (int j = i; j > 0 && nums[j] < nums[j - 1]; --j)
::swap(nums[j], nums[j - 1]);
}
}

1.4 归并排序

时间复杂度:O(nlogn) 空间复杂度:O(n) 稳定性:稳定

void MergeSort(vector& nums)
{
if (nums.size() <= 1)
return;
MergeSort_Process(nums, 0, nums.size() - 1);
}
void MergeSort_Process(vector& nums, int l, int r)
{
if (l >= r)
return;
int mid = l + ((r - l) >> 1);
MergeSort_Process(nums, l, mid);
MergeSort_Process(nums, mid + 1, r);
Merge(nums, l, r, mid);
}
void Merge(vector& nums, int l, int r, int mid)
{
int i = l, j = mid + 1;
vector temp(r - l + 1);
int k = 0;
while (i <= mid && j <= r)
{
if (nums[i] <= nums[j])
temp[k++] = nums[i++];
else
temp[k++] = nums[j++];
}
while (i <= mid)
temp[k++] = nums[i++];
while (j <= r)
temp[k++] = nums[j++];
::copy(temp.begin(), temp.end(), nums.begin() + l);
}

1.5 快速排序

时间复杂度:O(nlogn) 空间复杂度:O(logn) 稳定性:不稳定

void QuickSort(vector& nums)
{
if (nums.size() <= 1)
return;
srand((unsigned int)time(NULL));
QuickSort_Process(nums, 0, nums.size() - 1);
}
void QuickSort_Process(vector& nums, int l, int r)
{
if (l >= r)
return;
int pos = l + (rand() % (r - l + 1));
int sr = 0, br = 0;
Partition(nums, l, r, sr, br, pos);
QuickSort_Process(nums, l, sr);
QuickSort_Process(nums, br, r);
}
void Partition(vector& nums, int l, int r, int& sr, int& br, int pos)
{
int val = nums[pos];
sr = l - 1, br = r + 1;
int i = l;
while (i < br)
{
if (nums[i] < val)
::swap(nums[i++], nums[++sr]);
else if (nums[i] == val)
++i;
else
::swap(nums[i], nums[--br]);
}
}

1.6 堆排序

时间复杂度:O(nlogn) 空间复杂度:O(1) 稳定性:不稳定

void Heapify(vector& nums, int heapSize, int i = 0)
{
int left = 2 * i + 1, right = left + 1;
while (left < heapSize)
{
int maxIndex = (right < heapSize && nums[right] > nums[left]) ? right : left;
maxIndex = (nums[maxIndex] < nums[i]) ? i : maxIndex;
if (i == maxIndex)
return;
::swap(nums[i], nums[maxIndex]);
i = maxIndex;
left = 2 * i + 1;
right = left + 1;
}
}
void HeapSort(vector& nums)
{
if (nums.size() <= 1)
return;

int heapSize = nums.size();
for (int i = heapSize - 1; i >= 0; --i)
Heapify(nums, heapSize, i);

while (heapSize > 0)
{
::swap(nums[0], nums[--heapSize]);
Heapify(nums, heapSize);
}
}

1.7 基数排序

时间复杂度:O(n * k) 空间复杂度:O(n) 稳定性:稳定 (k为最大元素位数)

// 基数排序
int GetMaxDigit(const vector& nums)
{
int maxNum = ::max_element(nums.begin(), nums.end());
int res = 0;
while (maxNum != 0)
{
maxNum /= 10;
++res;
}
return res;
}
int GetDigit(int num, int d)
{
while (d > 1)
{
--d;
num /= 10;
}
return num % 10;
}
void RadixSort(vector& nums)
{
if (nums.size() <= 1)
return;
int d = GetMaxDigit(nums);
vector> buckets(10, queue());

for (int i = 0; i < d; ++i)
{
int index = 0;
for (int j = 0; j < nums.size(); ++j)
buckets[GetDigit(nums[j], i + 1)].push(nums[j]);
for (int j = 0; j < 10; ++j)
{
while (!buckets[j].empty())
{
nums[index++] = buckets[j].front();
buckets[j].pop();
}
}
}
}

2. 链表

技巧一:dummyHead

创建头结点指向首元结点,以减少针对于首元节点的大量特殊情况判断

  • 合并链表

ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode dummyHead;
ListNode* cur = &dummyHead, *p = list1, *q = list2;
while (p != nullptr && q != nullptr)
{
if (p->val <= q->val)
{
cur->next = p;
p = p->next;
}
else
{
cur->next = q;
q = q->next;
}
cur = cur->next;
}
if (p != nullptr)
cur->next = p;
if (q != nullptr)
cur->next = q;
return dummyHead.next;
}

  • 分割链表(链表partition)

ListNode* partition(ListNode* head, int x) {
if (head == nullptr)
return nullptr;
ListNode lessDummy, // 存放小于x的链表
greaterDummy; // 存放大于等于x的链表
ListNode* p1 = &lessDummy, *p2 = &greaterDummy;
ListNode* p = head;
while (p != nullptr)
{
if (p->val < x)
{
p1->next = p;
p1 = p1->next;
}
else
{
p2->next = p;
p2 = p2->next;
}
// 断开原链表的连接
ListNode* temp = p->next;
p->next = nullptr;
p = temp;
}
p1->next = greaterDummy.next;
return lessDummy.next;
}

技巧二:双指针(快慢指针)

快指针先走,慢指针后走(针对部分情况快指针一次走两步,慢指针一次走一步)

  • 删除链表倒数第k个结点

ListNode* removeNthFromEnd(ListNode* head, int n) {
if (head == nullptr || n <= 0)
return head;
ListNode* fast = head, *slow = head;
int count = 0;
while (count < n)
{
++count;
fast = fast->next;
}
if (fast == nullptr) // 倒数第n个结点
{
ListNode* res = head->next;
delete head;
head = nullptr;
return res;
}
while (fast->next != nullptr)
{
fast = fast->next;
slow = slow->next;
}
ListNode* pNext= slow->next;
slow->next = pNext->next;
delete pNext;
pNext = nullptr;

return head;
}

  • 链表中点

ListNode* middleNode(ListNode* head) {
if (head == nullptr)
return nullptr;
if (head->next == nullptr)
return head;
ListNode* fast = head, *slow = head;
while (fast != nullptr && fast->next != nullptr)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}

  • 链表入环结点

ListNode *detectCycle(ListNode *head) {
if (head == nullptr || head->next == nullptr)
return nullptr;

ListNode* fast = head, *slow = head;
do
{
if (fast == nullptr || fast->next == nullptr)
return nullptr;
fast = fast->next->next;
slow = slow->next;
} while (fast != slow);

slow = head;
while (slow != fast)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}

技巧三:哈希表

用哈希表记录结点的对应关系,但空间复杂度会提高

  • 链表的相交结点

// 1. 哈希表法
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_set record;
while (headA != nullptr)
{
record.insert(headA);
headA = headA->next;
}
while (headB != nullptr)
{
if (record.count(headB) != 0)
return headB;
headB = headB->next;
}
return nullptr;
}

// 2. 双指针法
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == nullptr || headB == nullptr)
return nullptr;
ListNode* p = headA, *q = headB;
int lenA = 0, lenB = 0;
while (p->next != nullptr)
{
p = p->next;
++lenA;
}
while (q->next != nullptr)
{
q = q->next;
++lenB;
}

// 无相交
if (p != q)
return nullptr;

int delta = abs(lenA - lenB);
p = (lenA > lenB) ? headA : headB;
q = (p == headA) ? headB : headA;

// 长的那根链表先走
while (delta > 0)
{
--delta;
p = p->next;
}
// 然后再一起走
while (p != q)
{
p = p->next;
q = q->next;
}
return p;
}

经典题目
  • 翻转链表

// 方法一:三指针法
ListNode* reverseList(ListNode* head) {
if (head == nullptr)
return nullptr;
ListNode* pPre = head, *pCur = head->next, *pNext;
pPre->next = nullptr;
while (pCur != nullptr)
{
pNext = pCur->next;
pCur->next = pPre;
pPre = pCur;
pCur = pNext;
}
return pPre;
}

// 方法二:头插法
ListNode* reverseList(ListNode* head) {
if (head == nullptr)
return nullptr;

ListNode* tail = head;
while (tail->next != nullptr)
tail = tail->next;

ListNode* p = head, *pNext;
while (p != tail)
{
pNext = p->next;
p->next = tail->next;
tail->next = p;
p = pNext;
}

return tail;
}

// 方法三:递归
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr)
return head;

ListNode* nextList = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return nextList;
}

  • 复杂链表的复制
/** 复杂链表结构定义如下:
* class Node {
* public:
* int val;
* Node* next;
* Node* random;
*
* Node(int _val) {
* val = _val;
* next = NULL;
* random = NULL;
* }
* };
*
*/

// 方法一:哈希表法 时间复杂度:O(n) 空间复杂度:O(n)
Node* copyRandomList(Node* head) {
if (head == nullptr)
return nullptr;
unordered_map record;
Node* cur = head;

// 构建原链表结点与拷贝链表结点的对应关系
while (cur != nullptr)
{
Node* copyNode = new Node(cur->val);
record.insert(make_pair(cur, copyNode));
cur = cur->next;
}

// 连接rand和next
cur = head;
while (cur != nullptr)
{
record[cur]->random = record[cur->random];
record[cur]->next = record[cur->next];
cur = cur->next;
}

return record[head];
}

// 方法二:链表关系映射(利用链表位置关系以代替哈希表) 时间复杂度:O(n) 空间复杂度:O(1)
Node* copyRandomList(Node* head) {
if (head == nullptr)
return nullptr;
Node* cur = head;

// 创建拷贝结点并连接在被拷贝的结点后 1 -> 1' -> 2' -> 2' -> ...
while (cur != nullptr)
{
Node* pNext = cur->next;
Node* copyNode = new Node(cur->val);
copyNode->next = cur->next;
cur->next = copyNode;
cur = pNext;
}

// 构建rand关系
cur = head;
while (cur != nullptr)
{
Node* copyNode = cur->next;
copyNode->random = (cur->random == nullptr) ? nullptr : cur->random->next;
cur = cur->next->next;
}

// 构建next
cur = head;
Node* res = head->next; // 保存结果,因为原链表在循环中会被恢复,不再指向copyNode
while (cur != nullptr)
{
Node* pNext = cur->next->next;
Node* copyNode = cur->next;
copyNode->next = (pNext == nullptr) ? nullptr : pNext->next;
cur->next = pNext; // 恢复原链表
cur = pNext;
}

return res;
}

3. 数组

技巧一:快慢指针

快指针探路,慢指针记录原位置,可减少数组中元素的搬动,以降低时间复杂度至O(n)

  • 删除数组中的重复项
int removeDuplicates(vector& nums) {
if (nums.size() <= 1)
return nums.size();
int slow = 0, fast = 1;
int len = nums.size();
int k = 0;
while (fast < len)
{
if (nums[slow] != nums[fast])
{
nums[k++] = nums[slow];
slow = fast;
}
++fast;
}
nums[k++] = nums[slow];
return k;
}
  • 移除0
void moveZeroes(vector& nums) {
if (nums.size() == 0)
return;
int fast = 0, slow = 0;
int len = nums.size();
while (fast < len)
{
if (nums[fast] != 0)
nums[slow++] = nums[fast];
++fast;
}
while (slow < len)
nums[slow++] = 0;
}
技巧二:partition思想

根据条件划分数组区域,通过遍历移动边界区域

  • 奇偶分区
vector exchange(vector& nums) {
if (nums.size() <= 1)
return nums;
int oddRange = -1, i = 0;
int len = nums.size();
while (i < len)
{
if ((nums[i] & 0x1) == 1) // 奇数
::swap(nums[i++], nums[++oddRange]);
else // 偶数
++i;
}
return nums;
}
技巧三:左右指针

左指针指向数组头,右指针指向数组尾,两指针向中间夹逼(或从中间向两端扩散)

  • 有序数组的两数之和
vector twoSum(vector& numbers, int target) {
int left = 0, right = numbers.size() - 1;
while (left < right)
{
if (numbers[left] + numbers[right] < target)
++left;
else if (numbers[left] + numbers[right] > target)
--right;
else
return vector{left + 1, right + 1};
}
return vector{0, 0};
}
  • 最长回文子串
string longestPalindrome(string s) {
string res;
for (int i = 0; i < s.size(); ++i)
{
// 奇数情况
string p1 = Palindrome(s, i, i);
// 偶数情况
string p2 = Palindrome(s, i, i + 1);
// 取较大者
res = res.size() > p1.size() ? res : p1;
res = res.size() > p2.size() ? res : p2;
}
return res;
}

string Palindrome(string s, int l, int r) // O(n) <=> O(1)
{
while (l >= 0 && r < s.size())
{
if (s[l] == s[r])
{
--l;
++r;
}
else
break;
}
return string(s.begin() + l + 1, s.begin() + r);
}
技巧四:从后向前

当数组空间足够大时,从后向前有时可避免数组元素的搬移,降低时间复杂度为O(n)

  • 替换空格
string replaceSpace(string s) {
int originSize = s.size();
int spaceCount = 0;
for (const auto& ch : s)
{
if (ch == ' ')
++spaceCount;
}
int newSize = originSize + 2 * spaceCount;
s.resize(newSize);
int i = originSize - 1, j = newSize - 1;
while (i >= 0)
{
if (s[i] == ' ')
{
s[j--] = '0';
s[j--] = '2';
s[j--] = '%';
--i;
}
else
s[j--] = s[i--];
}

return s;
}

4. 二叉树

思想一:递归序

一个二叉树结点在遍历过程中会被访问3次,3次访问过程都可完成相应操作,以实现不同的算法

// First visit --- PreOrder

Traverse(cur->lchild);

// Second visit --- InOrder

Traverse(cur->rchild);

// Third visit --- PostOrder
思想二:分解思想

针对当前结点,可以向左子树要信息,也可以向右子树要信息,最终需要合成出当前结点需要返回的信息,以构成完整的递归函数

struct Info
{
// data...
};

Info Process(Node* root)
{
// base-case
/* if () {} */

Info lData = Process(root->lchild); // 左子树要信息
Info rData = Process(root->rchild); // 右子树要信息

// 利用左子树信息和右子树信息合成当前信息
Info curData = /* ... */;

// 返回当前信息
return curData;
}
经典题目
  • 树的子结构
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
// base-case
if (pRoot1 == nullptr || pRoot2 == nullptr)
return false;

bool res = false;
if (pRoot1->val == pRoot2->val)
res = DoesTree1HaveTree2(pRoot1, pRoot2); // 根节点匹配,开始查看子树
if (res == false)
res = HasSubtree(pRoot1->left, pRoot2); // 匹配失败,查看左子树
if (res == false)
res = HasSubtree(pRoot1->right, pRoot2); // 又失败,查看右子树

return res;
}

bool DoesTree1HaveTree2(TreeNode* pRoot1, TreeNode* pRoot2)
{
if (pRoot2 == nullptr)
return true;
if (pRoot1 == nullptr)
return false;
if (pRoot1->val != pRoot2->val)
return false;

return DoesTree1HaveTree2(pRoot1->left, pRoot2->left)
&& DoesTree1HaveTree2(pRoot1->right, pRoot2->right);
}
  • 搜索二叉树的后序遍历序列
bool verifyPostorder(vector& postorder) {
if (postorder.size() == 0)
return true;
return verifyPostorder(postorder, 0, postorder.size() - 1);
}

bool verifyPostorder(vector& postorder, int l, int r)
{
int i = l;
int tar = postorder[r];
for (; i < r && postorder[i] < tar; ++i)
;
for (int j = i; j < r; ++j)
{
if (postorder[j] < tar)
return false;
}

bool leftRes = true;
if (i > l)
leftRes = verifyPostorder(postorder, l, i - 1);

bool rightRes = true;
if (i < r)
rightRes = verifyPostorder(postorder, i, r - 1);

return leftRes && rightRes;
}
  • 判断是否为平衡二叉树
bool isBalanced(TreeNode* root) {
return Process(root).isBT;
}

struct RType
{
int height;
bool isBT;
RType(int height, bool isBT) : height(height), isBT(isBT) {}
};

RType Process(TreeNode* root)
{
if (root == nullptr)
return RType(0, true);

// 左树信息
RType lData = Process(root->left);
// 右树信息
RType rData = Process(root->right);

// 合成当前信息
int height = 1 + ::max(lData.height, rData.height);
bool isBT = lData.isBT && rData.isBT && (abs(lData.height - rData.height) <= 1);

return RType(height, isBT);
}
  • 二叉搜索树与双向链表
TreeNode* Convert(TreeNode* pRootOfTree) {
TreeNode* pre = nullptr, *head = nullptr;
Process(pRootOfTree, pre, head);
return head;
}

void Process(TreeNode* cur, TreeNode* &pre, TreeNode* &head)
{
if (cur == nullptr)
return;

Process(cur->left, pre, head);

if (head == nullptr && cur->left == nullptr) // head-case
{
head = cur;
pre = cur;
}
else // other-case
{
cur->left = pre;
pre->right = cur;
pre = cur;
}


Process(cur->right, pre, head);
}
  • 二叉树的非递归遍历过程
// pre-order
vector preorderTraversal(TreeNode* root) {
if (root == nullptr)
return vector();
stack s;
s.push(root);
vector res;

TreeNode* cur;
while (!s.empty())
{
cur = s.top();
s.pop();
res.push_back(cur->val);
if (cur->right)
s.push(cur->right);
if (cur->left)
s.push(cur->left);
}

return res;
}

// post-order
vector postorderTraversal(TreeNode* root) {
if (root == nullptr)
return vector();
stack s1, s2;
s1.push(root);
vector res;

TreeNode* cur;
while (!s1.empty())
{
cur = s1.top();
s1.pop();
s2.push(cur);
if (cur->left)
s1.push(cur->left);
if (cur->right)
s1.push(cur->right);
}

while (!s2.empty())
{
cur = s2.top();
s2.pop();
res.push_back(cur->val);
}

return res;
}

// in-order
vector inorderTraversal(TreeNode* root) {
stack s;
vector res;
while (root != nullptr || !s.empty())
{
if (root != nullptr)
{
s.push(root);
root = root->left;
}
else
{
root = s.top();
s.pop();
res.push_back(root->val);
root = root->right;
}
}
return res;
}

5. 暴力递归

暴力递归,即回溯思想,其本质思想同递归序,根据进入结点的时机不同完成相应的操作

基本框架如下:

process() {
// base-case
...

// 处理当前信息
...

// 处理后续信息
process() ...

// 回退状态至当前状态
...
}
经典题目
  • 子集问题

输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

vector> res;    

vector> subsets(vector& nums) {
res.push_back(vector()); // 空集情况
for (int i = 0; i < nums.size(); ++i)
{
vector temp;
process(nums, i, temp);
}
return res;
}

void process(vector& nums, int index, vector& temp)
{
// 处理当前信息
temp.push_back(nums[index]);
res.push_back(temp);

for (int i = index + 1; i < nums.size(); ++i) // base-case
{
// 处理后续信息
process(nums, i, temp);
// 恢复状态
temp.pop_back();
}
}
  • 全排列问题(有重复值情况)
vector> res;

vector> permuteUnique(vector& nums) {
::sort(nums.begin(), nums.end());
vector record(nums.size(), false);
vector temp;
process(nums, temp, record);
return res;
}

void process(vector& nums, vector& temp, vector& record)
{
if (temp.size() == nums.size())
{
res.push_back(temp);
return;
}

for (int i = 0; i < nums.size(); ++i)
{
if (record[i])
continue;
if (i > 0 && nums[i] == nums[i - 1] && !record[i - 1]) // 剪枝逻辑
continue;
temp.push_back(nums[i]);
record[i] = true;
process(nums, temp, record);
record[i] = false;
temp.pop_back();
}
}

6. 动态规划

动态规划问题的特点:

  1. 具备最优子结构,即是否能够通过子问题的最值得到原问题的最值
  2. 存在重叠子问题,即存在大量重复的问题,可通过记录表(如布尔数组、哈希表等)记录返回结果并在下一次需要的时候直接使用
步骤一:自顶向下的暴力尝试
  1. 分析base-case
  2. 罗列可能性
  3. 得到子问题结果
  4. 处理返回当前问题结果

以**​​不同路径问题​​**为例:

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?(来源:Leetcode. 62)

示例:

​输入:m = 3, n = 7 输出:28 ​​​​输入:m = 3, n = 2 输出:3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。 1. 向右 -> 向下 -> 向下 2. 向下 -> 向下 -> 向右 3. 向下 -> 向右 -> 向下 ​

int process(int m, int n, int x, int y)
{
// base-case
if (x > m || y > n)
return 0;
if ((x == m) && (y == n))
return 1;

// 可能性罗列
int p1 = process(m, n, x + 1, y);
int p2 = process(m, n, x, y + 1);

// 返回当前结果
return p1 + p2;
}
步骤二:自顶向下的记忆化搜索

由于递归过程中存在大量的重复计算,这些计算结果可以用一张记录表来记录,当第二次使用数据时可直接拿到结果,以提升效率

仍以上述题目为例:

int uniquePaths(int m, int n) {
vector> record(m + 1, vector(n + 1, -1));
return process(m, n, 1, 1, record);
}


int process(int m, int n, int x, int y, vector>& record)
{
// base-case
if (x > m || y > n)
return 0;
else if ((x == m) && (y == n))
record[x][y] = 1;
else
{
// 已知record的数据,直接返回
if (record[x][y] != -1)
return record[x][y];

// 可能性罗列
int p1 = process(m, n, x + 1, y, record);
int p2 = process(m, n, x, y + 1, record);
record[x][y] = p1 + p2;
}

// 返回当前结果
return record[x][y];
}
步骤三:自底向上的动态规划

根据暴力尝试的结果,将其翻译为dp的代码:

  1. 尝试过程的变量即为dp表的可变范围,如一个变量为一维数组,两个变量为二维数组等
  2. base-case直接对应dp表的位置进行赋值
  3. 遍历dp表以构建数据结果,构建的过程为递归过程的逆过程
  4. 递归函数转化为dp表赋值,即:process(x, y) <=> dpTable\[x]\[y]
  5. 参照入口递归函数的参数返回最终结果,即:process(0, 0) <=> return dpTable\[0]\[0]

仍以上述题目为例:

int uniquePaths(int m, int n) {
return dp(m, n);
}

int dp(int m, int n)
{
// 建立变量为x和y的dp表
vector> dpTable(m + 2, vector(n + 2, 0));
// 自底向上构建结果
for (int x = m; x > 0; --x)
{
for (int y = n; y > 0; --y)
{
// base-case
if (x == m && y == n)
dpTable[m][n] = 1;
else
{
int p1 = dpTable[x + 1][y];
int p2 = dpTable[x][y + 1];
dpTable[x][y] = p1 + p2;
}
}
}

return dpTable[1][1];
}
特别声明:以上内容(图片及文字)均为互联网收集或者用户上传发布,本站仅提供信息存储服务!如有侵权或有涉及法律问题请联系我们。
举报
评论区(0)
按点赞数排序
用户头像
精选文章
thumb 中国研究员首次曝光美国国安局顶级后门—“方程式组织”
thumb 俄乌线上战争,网络攻击弥漫着数字硝烟
thumb 从网络安全角度了解俄罗斯入侵乌克兰的相关事件时间线
下一篇
冒泡排序算法(10) 2022-11-19 06:01:29