1. 程式人生 > 其它 >程式設計之美閱讀筆記

程式設計之美閱讀筆記

1. 判斷一個點是否在三角形內部

常用的tirck:計算三部分面積和,由於浮點數有精度問題,可能會出錯
還有其他方法嗎??
可以利用叉乘

如果在三角形內部,沿著邊逆時針走,D一定都是在左邊;或者順時針走,D一定都在右邊
而判斷點線上的左邊還是右邊 可以用叉乘

點選檢視程式碼
#include<iostream>
using namespace std;

struct Point
{
    int x;
    int y;
    Point(int x, int y) : x(x), y(y) {}
};

// Function to find orientation of ordered triplet (p, q, r).
// p1p2 x p1p3
int CrossProduct(Point p1, Point p2, Point p3)
{
    return (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x);
}

int PointInTriangle(Point p, Point p1, Point p2, Point p3)
{
    // Find orientation
    int o1 = CrossProduct(p1, p2, p);
    int o2 = CrossProduct(p2, p3, p);
    int o3 = CrossProduct(p3, p1, p);

    // If p is colinear with at least 2 points, then check if it lies
    // in the triangle or not
    if (o1 == 0 && o2 == 0 && o3 == 0)
    {
        if (p.x <= max(p1.x, max(p2.x, p3.x)) && p.x >= min(p1.x, min(p2.x, p3.x)) &&
            p.y <= max(p1.y, max(p2.y, p3.y)) && p.y >= min(p1.y, min(p2.y, p3.y)))
            return 1;
        else
            return 0;
    }

    // Check if p is in triangle or not
    return (o1 >= 0 && o2 >= 0 && o3 >= 0) || (o1 <= 0 && o2 <= 0 && o3 <= 0);
}

int main() {
    Point p1(0, 0), p2(10, 0), p3(5,8);
    Point d(-1,0);
    if(PointInTriangle(d, p1, p2, p3))
        cout << "The point is inside the triangle";
    else
        cout << "The point is outside the triangle";

    return 0;
}

2. 將一個數組分成兩個等長的子陣列,要求兩者的和儘可能接近

看似簡單,實則是近期遇到的思考最久的了
例如,我們可以暴搜得到k個,更新最接近的值,複雜度 \(n^{n/2}\)
仔細想,這是一個揹包問題,2n個物品,選取n個,使得值儘可能接近一半
通常揹包問題中 dp[i][j] 表示前i個物品體積為j時的 最大價值/能夠裝下,但是這沒有保證物品的個數
加入我們定義bool dp[i][j] 為 i個物品 sum為j 是否可能,依次考慮每個物品的貢獻,當第 k 個物品時,對之前每一行都要更新,例如根據第二行(也就是隻有2個物品),更新第三行(表示物品數目為3)

與普通的揹包相比,之前的每一行都是有用的,而普通揹包只用了前一行,通常可以用滾動陣列省掉

點選檢視程式碼
    void canPartitionLike(vector<int>& nums) {
        int sum = 0, n = nums.size();
        for(int num : nums) sum += num;
        bool dp[n+1][sum/2+1];
        memset(dp, 0, sizeof(dp));
        cout << "dp...." << endl;
        // for(int i = 0; i <= n; i++) dp[i][0] = true;
        dp[0][0] = true;
        for(int i = 1; i <= n; i++) {  // 依次考慮每個物品
            for(int j = 1; j <= sum/2; j++) {  // 每個和
                for(int k = i; k > 0; k--) {   // 每一行
                    if(j >= nums[i-1] && dp[k-1][j-nums[i-1]])
                        dp[k][j] = true;
               }
            }
        }

        cout << "****" << endl;

        for(int i = 0; i <= n; i++) {
            for(int j = 0; j <= sum/2; j++) {
                cout << dp[i][j] << " ";
            }
            cout << endl;
        }

        for(int i = sum/2; i >= 0; i--) {
            if(dp[n/2][i]) {
                cout << i << endl;
                break;
            }
        }
    }

3. 將陣列迴圈右移 k 位

例如 abcd1234
右移1位:4abcd123
右移2位:34abcd12
右移3位:234abcd1

可以發現規律,雖然右移了,但是右移結果都可以劃分成原來順序兩段,例如 34 abcd12
所以,可以先劃分,再整體翻轉,再部分翻轉

點選檢視程式碼
void Reverse(vector<int>& nums, int start, int end) {
    while (start < end) {
        swap(nums[start], nums[end]);
        start++;
        end--;
    }
}

vector<int> RightShiftK3(vector<int>& nums, int k) {
    Reverse(nums, 0, nums.size() - 1);
    Reverse(nums, 0, k - 1);
    Reverse(nums, k, nums.size() - 1);
    return nums;
}

4. 子陣列的最大乘積

給定一個長度為N的陣列,不允許用除法,計算任意N-1個元素的最大乘積

解法:
解法一:列舉所有組合n * 每個組合計算乘積 n,複雜度\(O(n^2)\)
解法二:列舉不乘的元素,將左右兩邊的乘積相乘,字首和字尾可以預處理,複雜度\(O(n)\)
解法三:分類討論

5. 不要被階乘嚇倒

問題1:求 N! 末尾有多少個0?
問題2:求 N! 的二進位制表示中最低位1的位置

問題1的解法:就是1~n中"5"的出現次數,因為"2"肯定比5多
res = N/5 + N/25 + N/125...

問題2的解法:最低位1的位置就是"2"的次數
而"2"的次數等於 N/2 + N/4 + N/8...

6. 判斷連結串列是否相交

解法一:連線成環,將A的尾接到B的頭,再從A或B出發
加入限定是"Y"型
可以用解法二:A、B都走到最末尾,判斷兩個末尾節點是否相同

個性簽名:時間會解決一切