手撕算法稳过技巧3-log的细节

56 阅读4分钟

面试算法log的技巧

之前已经写过两篇文章

在这一次文章的文章中,我总结了手撕算法的几个关键技巧, 分别是:

  1. 循环的开始和结尾要打日志,可以观察每次循环做了什么
  2. 关键的思路配合关键的参数那里要打日志,对于复杂的多次决策更应该如此
  3. 日志要写的可以读,高质量的日志,像自然语言一样帮助分析思路!!!
  4. 对于关键的数据结构,可以使用专门的辅助方法来打印,从而方便观察

以颜色分类为例

  • @lc app=leetcode.cn id=75 lang=cpp

  • @lcpr version=30204

  • [75] 颜色分类

  • leetcode.cn/problems/so…

  • algorithms

  • Medium (63.21%)

  • Likes: 2000

  • Dislikes: 0

  • Total Accepted: 868.8K

  • Total Submissions: 1.4M

  • Testcase Example: '[2,0,2,1,1,0]

  • 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

  • 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

  • 必须在不使用库内置的 sort 函数的情况下解决这个问题。

  • 示例 1:

  • 输入:nums = [2,0,2,1,1,0]

  • 输出:[0,0,1,1,2,2]

  • 示例 2:

  • 输入:nums = [2,0,1]

  • 输出:[0,1,2]

  • 提示:

  • n == nums.length

  • 1 <= n <= 300

  • nums[i] 为 0、1 或 2

  • 进阶:

  • 你能想出一个仅使用常数空间的一趟扫描算法吗?

拿道题后,我们需要先在草稿上推理几遍,这是我拿例子推演的过程:

//  * 
//  * 输入:nums = [2,0,2,1,1,0]
//  * 输出:[0,0,1,1,2,2]
//  * 

// [2,0,2,1,1,0] 

// [0,0,2,1,1,2] 0,2对调,p2指向2
// [0,0,2,1,1,2] p0 指向2, p指向2
// [0,0,1,1,2,2] p0 指向2, p指向2

//  * 输入:nums = [2,0,1]
//  * 输出:[0,1,2]
//  * 

// [2,0,1] 移动2的指针
// [1,0,2] 对调2
// [1,0,2]
// [0,1,2]

我认为,还要记得几项重要的事,多想一些边缘情况,Corner Case.
比如 [0],[1] [2,2,2]之类的特殊例子。

// @lcpr-template-start
using namespace std;
#include <algorithm>
#include <array>
#include <bitset>
#include <climits>
#include <deque>
#include <functional>
#include <iostream>
#include <list>
#include <queue>
#include <stack>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
// @lcpr-template-end
// @lc code=start
class Solution {
public:
    void sortColors(vector<int>& nums) {
        int p0 = 0, p = 0, p2 = nums.size() - 1;
        while(true){
            cout << "---round go " << p << endl;
            cout << "round p2 " << p2 << endl;
            cout << "round p0 " << p0 << endl;
            cout << "round p " << p << endl;
            if(p2 < p || p2 < 0 ){
                break;
            }
            cout << "Try move p2 forward" << endl;
            while(p2 >= 0 && nums[p2] == 2){
                p2 -= 1;
            }
            cout << "Try move p2 forward done p2 " << p2 << endl;
            print_helper(nums);

            cout << "Try move 2 at p backward" << endl;
            cout << "Try move 2 at p backward p is " << p << endl;
            if(p < p2 && p2 >= 0 &&  nums[p] == 2){
                swap(nums[p], nums[p2]);
                p2 -= 1;
            }
            cout << "Try move 2 at p backward done p2 " << p2 << endl;
            print_helper(nums);

            if(nums[p]==0){ 
                swap(nums[p], nums[p0]);
                p0 += 1;
            }
            p += 1;
            cout << "round done p2 " << p2 << endl;
            cout << "round done p0 " << p0 << endl;
            cout << "round done p " << p << endl;
            print_helper(nums);
            cout << "---" << endl;
        }
    }


    void print_helper(vector<int> &nums){
        cout <<  "num is: " ; 
        int i = 0;
        for(auto item: nums){
            cout <<  "[" << i << "]" << item << ", ";
            i++;
        }
        cout << endl;
    }
};

以轮转数组为例

/* @lc app=leetcode.cn id=189 lang=cpp
@lcpr version=30204

[189] 轮转数组

leetcode.cn/problems/ro…

algorithms
Medium (47.35%)
Likes: 2456
Dislikes: 0
Total Accepted: 1.3M
Total Submissions: 2.8M
Testcase Example: '[1,2,3,4,5,6,7] 3'

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 1:

输入: nums = [1,2,3,4,5,6,7], k = 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,1,2,3,4]

示例 2:

输入:nums = [-1,-100,3,99], k = 2 输出:[3,99,-1,-100] 解释: 向右轮转 1 步: [99,-1,-100,3] 向右轮转 2 步: [3,99,-1,-100]

提示:

1 <= nums.length <= 10^5 -2^31 <= nums[i] <= 2^31 - 1 0 <= k <= 10^5

进阶:

尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。 你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?

本次创造性的提出了数组的打印方法:

void print_helper(vector<int>& nums, string tip = ""){
    cout << tip << "print nums: " << endl ;
    cout << "Index ";
    for(int i=0; i<nums.size(); i++){
        cout << i << ", ";
    }
    cout << endl;
    cout << "Value ";
    for(int i=0; i<nums.size(); i++){
        cout << nums[i] << ", ";
    }
    cout << endl;
}

以下是完整解法:


// @lcpr-template-start
using namespace std;
#include <algorithm>
#include <array>
#include <bitset>
#include <climits>
#include <deque>
#include <functional>
#include <iostream>
#include <list>
#include <queue>
#include <stack>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
// @lcpr-template-end
// @lc code=start
class Solution {
public:

// [1,2,3,4,5,6,7,], k = 3
// [5,6,7,1,2,3,4], 从后往前数3个数,切成两半,直接弄
// [4,3,2,1,7,6,5] 都局部翻转,再全部翻转
// [5,6,7,1,2,3,4]

// k=4,同理
// k=7,不弄了

// k = k%nums.size()
// k = 0 return
    void rotate(vector<int>& nums, int k) {
        k = k % nums.size();
        if(k == 0) return;
        cout << "Real k is " << k << endl;
        print_helper(nums, "Init ");
        int start_len = nums.size() - k;
        cout << "Start len is " << start_len << endl;
        cout << "Start reverse..." << endl;
        reverse(nums.begin() + start_len, nums.end());
        print_helper(nums, "First reverse ");
        reverse(nums.begin(), nums.begin() + start_len);
        print_helper(nums, "Second reverse ");
        reverse(nums.begin(), nums.end());
        print_helper(nums, "Third reverse ");
        cout << "--------" << endl;
    }
    
    void print_helper(vector<int>& nums, string tip = ""){
        cout << tip << "print nums: " << endl ;
        cout << "Index ";
        for(int i=0; i<nums.size(); i++){
            cout << i << ", ";
        }
        cout << endl;
        cout << "Value ";
        for(int i=0; i<nums.size(); i++){
            cout << nums[i] << ", ";
        }
        cout << endl;
    }
};