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

常见算法总结

1. 排序算法

1.1 选择排序

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

void SelectSort(vector<int>& 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<int>& 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<int>& 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<int>& nums)
{
    if (nums.size() <= 1)
        return;
    MergeSort_Process(nums, 0, nums.size() - 1);
}
void MergeSort_Process(vector<int>& 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<int>& nums, int l, int r, int mid)
{
    int i = l, j = mid + 1;
    vector<int> 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<int>& nums)
{
    if (nums.size() <= 1)
        return;
    srand((unsigned int)time(NULL));
    QuickSort_Process(nums, 0, nums.size() - 1);
}
void QuickSort_Process(vector<int>& 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<int>& 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<int>& 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<int>& 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<int>& 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<int>& nums)
{
    if (nums.size() <= 1)
        return;
    int d = GetMaxDigit(nums);
    vector<queue<int>> buckets(10, queue<int>());

    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<ListNode*> 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<Node*, Node*> 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<int>& 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<int>& 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<int> exchange(vector<int>& 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<int> twoSum(vector<int>& 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<int>{left + 1, right + 1};
    }
    return vector<int>{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<int>& postorder) {
    if (postorder.size() == 0)
        return true;
    return verifyPostorder(postorder, 0, postorder.size() - 1);
}

bool verifyPostorder(vector<int>& 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<int> preorderTraversal(TreeNode* root) {
    if (root == nullptr)
        return vector<int>();
    stack<TreeNode*> s;
    s.push(root);
    vector<int> 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<int> postorderTraversal(TreeNode* root) {
    if (root == nullptr)
        return vector<int>();
    stack<TreeNode*> s1, s2;
    s1.push(root);
    vector<int> 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<int> inorderTraversal(TreeNode* root) {
    stack<TreeNode*> s;
    vector<int> 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<vector<int>> res;    

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

void process(vector<int>& nums, int index, vector<int>& 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<vector<int>> res;

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

void process(vector<int>& nums, vector<int>& temp, vector<bool>& 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<vector<int>> record(m + 1, vector<int>(n + 1, -1));
    return process(m, n, 1, 1, record);
}


int process(int m, int n, int x, int y, vector<vector<int>>& 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<vector<int>> dpTable(m + 2, vector<int>(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];
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值