程式設計之美閱讀筆記
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都走到最末尾,判斷兩個末尾節點是否相同