re安裝wordpress短視頻seo軟件
996 預(yù)防針
隨著秋招進(jìn)程的不斷推進(jìn),有部分同學(xué)已經(jīng) OC,有部分同學(xué)還在苦苦掙扎,并不斷降低自己的預(yù)期,包括在和 HR 溝通過程中,主動(dòng)說出自己愿意接受加班,愿意接受 996,以此來博得企業(yè)方面的加分。
我的建議是,不要這么做。
不要以為 996 是開玩笑。如果在工位上的時(shí)間都已經(jīng)是 996,那么投入到這份工作上的時(shí)間只會(huì)更多。
應(yīng)屆生一般經(jīng)濟(jì)情況不算寬裕,直接住在公司邊上的概率較低(園區(qū)附近房子租金較高),通勤時(shí)間就算半個(gè)小時(shí)吧,這意味著 8 點(diǎn)左右必須起床,8 點(diǎn) 30 必須出發(fā),為了避免遲到,實(shí)際情況還會(huì)比這個(gè)時(shí)間點(diǎn)更早;晚上下班算你 9 點(diǎn)準(zhǔn)時(shí)走,到家 9 點(diǎn) 30,基本洗漱結(jié)束后差不多就是 11 點(diǎn)了??梢哉f一天下來,娛樂和休息兩者不可兼得。
更殘忍的是,這樣的節(jié)奏一周有 6 天,一周休息兩天和休息一天體驗(yàn)上千差萬別。雙休本質(zhì)上是有完全放松的一天(周六),但單休你只能抓緊時(shí)間休息,而不敢安排其他活動(dòng),因?yàn)槟氵€要考慮第二天 8 點(diǎn)起床的問題。
這就是 996 作為日常的真實(shí)分析,而非停留在口號(hào)上的感受,沒試過的東西,永遠(yuǎn)不要覺得自己能輕易承受。
再強(qiáng)調(diào)一遍:不要以為 996 是開玩笑,不要輕易將"愿意 996"掛在嘴邊??赡茉灸隳苓M(jìn)入一個(gè)作息正常的部門,但因?yàn)槟阒鲃?dòng)強(qiáng)調(diào)自己接受 996,HR 會(huì)因此另外安排你去比較卷的部門,畢竟你自己說的愿意熬。
996 作為了一個(gè)出現(xiàn)了幾年,但還在不斷被提起的長青詞,你真的體驗(yàn)過嗎?你的工作時(shí)間是什么?你對(duì) 996 的看法又是什么?一起評(píng)論區(qū)交流呀。
...
回歸主題。
來一道和「校招」相關(guān)的算法原題。
題目描述
平臺(tái):LeetCode
題號(hào):368
給你一個(gè)由無重復(fù)正整數(shù)組成的集合 nums
,請(qǐng)你找出并返回其中最大的整除子集 answer
,子集中每一元素對(duì) 都應(yīng)當(dāng)滿足:answer[i] % answer[j] = 0
或 answer[j] % answer[i] = 0
。
如果存在多個(gè)有效解子集,返回其中任何一個(gè)均可。
示例 1:
輸入:nums?=?[1,2,3]
輸出:[1,2]
解釋:[1,3]?也會(huì)被視為正確答案。
示例 2:
輸入:nums?=?[1,2,4,8]
輸出:[1,2,4,8]
提示:
-
nums
中的所有整數(shù)互不相同
基本分析
根據(jù)題意:「對(duì)于符合要求的「整除子集」中的任意兩個(gè)值,必然滿足「較大數(shù)」是「較小數(shù)」的倍數(shù)?!?/strong>
數(shù)據(jù)范圍是 ,我們不可能采取獲取所有子集,再檢查子集是否合法的爆搜解法。
通?!高f歸」做不了,我們就往「遞推」方向去考慮。
「由于存在「整除子集」中任意兩個(gè)值必然存在倍數(shù)/約數(shù)關(guān)系的性質(zhì),我們自然會(huì)想到對(duì) nums
進(jìn)行排序,然后從集合 nums
中從大到小進(jìn)行取數(shù),每次取數(shù)只考慮當(dāng)前決策的數(shù)是否與「整除子集」中的最后一個(gè)數(shù)成倍數(shù)關(guān)系即可。」
這時(shí)候你可能會(huì)想枚舉每個(gè)數(shù)作為「整除子集」的起點(diǎn),然后從前往后遍歷一遍,每次都將符合「與當(dāng)前子集最后一個(gè)元素成倍數(shù)」關(guān)系的數(shù)加入答案。
舉個(gè)🌰,假設(shè)有原數(shù)組 [1,2,4,8]
,“或許”我們期望的決策過程是:
-
遍歷到數(shù)字 1
,此時(shí)「整除子集」為空,加到「整除子集」中; -
遍歷到數(shù)字 2
,與「整除子集」的最后一個(gè)元素(1
)成倍數(shù)關(guān)系,加到「整除子集」中; -
遍歷到數(shù)字 4
,與「整除子集」的最后一個(gè)元素(2
)成倍數(shù)關(guān)系,自然也與2
之前的元素成倍數(shù)關(guān)系,加到「整除子集」中; -
遍歷到數(shù)字 8
,與「整除子集」的最后一個(gè)元素(4
)成倍數(shù)關(guān)系,自然也與4
之前的元素成倍數(shù)關(guān)系,加到「整除子集」中。
「但這樣的做法只能夠確保得到「合法解」,無法確保得到的是「最長整除子集」」。
當(dāng)時(shí)擔(dān)心本題數(shù)據(jù)太弱,上述錯(cuò)誤的解法也能夠通過,所以還特意實(shí)現(xiàn)了一下,還好被卡住了(🤣
同時(shí)也得到這個(gè)反例:[9,18,54,90,108,180,360,540,720]
,如果按照我們上述邏輯,我們得到的是 [9,18,54,108,540]
答案(長度為 5),但事實(shí)上存在更長的「整除子集」: [9,18,90,180,360,720]
(長度為 6)。
「其本質(zhì)是因?yàn)橥粋€(gè)數(shù)的不同倍數(shù)之間不存在必然的「倍數(shù)/約數(shù)關(guān)系」,而只存在「具有公約數(shù)」的性質(zhì),這會(huì)導(dǎo)致我們「模擬解法」錯(cuò)過最優(yōu)解。」
比如上述 🌰,54
& 90
和 18
存在倍數(shù)關(guān)系,但兩者本身不存在倍數(shù)關(guān)系。
因此當(dāng)我們決策到某一個(gè)數(shù) nums[i]
時(shí)(nums
已排好序),我們無法直接將 nums[i]
直接接在符合「約數(shù)關(guān)系」的、最靠近位置 i
的數(shù)后面,而是要「檢查位置 i
前面的所有符合「約數(shù)關(guān)系」的位置,找一個(gè)已經(jīng)形成「整除子集」長度最大的數(shù)」。
「換句話說,當(dāng)我們對(duì) nums
排好序并從前往后處理時(shí),在處理到 nums[i]
時(shí),我們希望知道位置 i
之前的下標(biāo)已經(jīng)形成的「整除子集」長度是多少,然后從中選一個(gè)最長的「整除子集」,將 nums[i]
接在后面(前提是符合「倍數(shù)關(guān)系」)?!?/strong>
動(dòng)態(tài)規(guī)劃
基于上述分析,我們不難發(fā)現(xiàn)這其實(shí)是一個(gè)序列 DP 問題:「某個(gè)狀態(tài)的轉(zhuǎn)移依賴于與前一個(gè)狀態(tài)的關(guān)系。即 nums[i]
能否接在 nums[j]
后面,取決于是否滿足 nums[i] % nums[j] == 0
條件。」
可看做是「最長上升子序列」問題的變形題。
「定義 為考慮前 i
個(gè)數(shù)字,且以第 i
個(gè)數(shù)為結(jié)尾的最長「整除子集」長度?!?/strong>
我們不失一般性的考慮任意位置 i
,存在兩種情況:
-
如果在 i
之前找不到符合條件nums[i] % nums[j] == 0
的位置j
,那么nums[i]
不能接在位置i
之前的任何數(shù)的后面,只能自己獨(dú)立作為「整除子集」的第一個(gè)數(shù),此時(shí)狀態(tài)轉(zhuǎn)移方程為 ; -
如果在 i
之前能夠找到符合條件的位置j
,則取所有符合條件的f[j]
的最大值,代表如果希望找到以nums[i]
為結(jié)尾的最長「整除子集」,需要將nums[i]
接到符合條件的最長的nums[j]
后面,此時(shí)狀態(tài)轉(zhuǎn)移方程為 。
同時(shí)由于我們需要輸出具體方案,需要額外使用 g[]
數(shù)組來記錄每個(gè)狀態(tài)是由哪個(gè)狀態(tài)轉(zhuǎn)移而來。
「定義 為記錄 是由哪個(gè)下標(biāo)的狀態(tài)轉(zhuǎn)移而來,如果 , 則有 ?!?/strong>
對(duì)于求方案數(shù)的題目,多開一個(gè)數(shù)組來記錄狀態(tài)從何轉(zhuǎn)移而來是最常見的手段。
當(dāng)我們求得所有的狀態(tài)值之后,可以對(duì) f[]
數(shù)組進(jìn)行遍歷,取得具體的最長「整除子集」長度和對(duì)應(yīng)下標(biāo),然后使用 g[]
數(shù)組進(jìn)行回溯,取得答案。
Java 代碼:
class?Solution?{
????public?List<Integer>?largestDivisibleSubset(int[]?nums)?{
????????Arrays.sort(nums);
????????int?n?=?nums.length;
????????int[]?f?=?new?int[n],?g?=?new?int[n];
????????for?(int?i?=?0;?i?<?n;?i++)?{
????????????//?至少包含自身一個(gè)數(shù),因此起始長度為?1,由自身轉(zhuǎn)移而來
????????????int?len?=?1,?prev?=?i;
????????????for?(int?j?=?0;?j?<?i;?j++)?{
????????????????if?(nums[i]?%?nums[j]?==?0)?{
????????????????????//?如果能接在更長的序列后面,則更新「最大長度」&「從何轉(zhuǎn)移而來」
????????????????????if?(f[j]?+?1?>?len)?{
????????????????????????len?=?f[j]?+?1;
????????????????????????prev?=?j;
????????????????????}
????????????????}
????????????}
????????????//?記錄「最終長度」&「從何轉(zhuǎn)移而來」
????????????f[i]?=?len;?g[i]?=?prev;
????????}
????????//?遍歷所有的?f[i],取得「最大長度」和「對(duì)應(yīng)下標(biāo)」
????????int?max?=?-1,?idx?=?-1;
????????for?(int?i?=?0;?i?<?n;?i++)?{
????????????if?(f[i]?>?max)?{
????????????????idx?=?i;
????????????????max?=?f[i];
????????????}
????????}
????????//?使用?g[]?數(shù)組回溯出具體方案
????????List<Integer>?ans?=?new?ArrayList<>();
????????while?(ans.size()?!=?max)?{
????????????ans.add(nums[idx]);
????????????idx?=?g[idx];
????????}
????????return?ans;
????}
}
C++ 代碼:
class?Solution?{
public:
????vector<int>?largestDivisibleSubset(vector<int>&?nums)?{
????????sort(nums.begin(),?nums.end());
????????int?n?=?nums.size();
????????vector<int>?f(n,?0),?g(n,?0);
????????for?(int?i?=?0;?i?<?n;?i++)?{
????????????int?len?=?1,?prev?=?i;
????????????for?(int?j?=?0;?j?<?i;?j++)?{
????????????????if?(nums[i]?%?nums[j]?==?0)?{
????????????????????if?(f[j]?+?1?>?len)?{
????????????????????????len?=?f[j]?+?1;
????????????????????????prev?=?j;
????????????????????}
????????????????}
????????????}
????????????f[i]?=?len;?g[i]?=?prev;
????????}
????????int?max?=?-1,?idx?=?-1;
????????for?(int?i?=?0;?i?<?n;?i++)?{
????????????if?(f[i]?>?max)?{
????????????????max?=?f[i];
????????????????idx?=?i;
????????????}
????????}
????????vector<int>?ans;
????????while?(ans.size()?!=?max)?{
????????????ans.push_back(nums[idx]);
????????????idx?=?g[idx];
????????}
????????return?ans;
????}
};
Python 代碼:
class?Solution:
????def?largestDivisibleSubset(self,?nums:?List[int])?->?List[int]:
????????nums.sort()
????????n?=?len(nums)
????????f,?g?=?[0]?*?n,?[0]?*?n
????????for?i?in?range(n):
????????????lenv,?prev?=?1,?i
????????????for?j?in?range(i):
????????????????if?nums[i]?%?nums[j]?==?0:
????????????????????if?f[j]?+?1?>?lenv:
????????????????????????lenv,?prev?=?f[j]?+?1,?j
????????????f[i],?g[i]?=?lenv,?prev
????????maxv,?idx?=?-1,?-1
????????for?i?in?range(n):
????????????if?f[i]?>?maxv:
????????????????maxv,?idx?=?f[i],?i
????????ans?=?[]
????????while?len(ans)?!=?maxv:
????????????ans.append(nums[idx])
????????????idx?=?g[idx]
????????return?ans
-
時(shí)間復(fù)雜度: -
空間復(fù)雜度:
證明
「之所以上述解法能夠成立,問題能夠轉(zhuǎn)化為「最長上升子序列(LIS)」問題進(jìn)行求解,本質(zhì)是利用了「全序關(guān)系」中的「可傳遞性」?!?/strong>
在 LIS 問題中,我們是利用了「關(guān)系運(yùn)算符 」的傳遞性,因此當(dāng)我們某個(gè)數(shù) a
能夠接在 b
后面,只需要確保 成立,即可確保 a
大于等于 b
之前的所有值。
那么同理,如果我們想要上述解法成立,我們還需要證明如下內(nèi)容:
「倍數(shù)/約數(shù)關(guān)系」具有傳遞性
由于我們將 nums[i]
往某個(gè)數(shù)字后面接時(shí)(假設(shè)為 nums[j]
),只檢查了其與 nums[j]
的關(guān)系,并沒有去檢查 nums[i]
與 nums[j]
之前的數(shù)值是否具有「倍數(shù)/約數(shù)關(guān)系」。
換句話說,我們只確保了最終答案 [a1, a2, a3, ..., an]
相鄰兩數(shù)值之間具有「倍數(shù)/約數(shù)關(guān)系」,并不明確任意兩值之間具有「倍數(shù)/約數(shù)關(guān)系」。
因此需要證得由 和 ,可推導(dǎo)出 的傳遞性:
由 可得 由 可得
最終有 ,由于 和 都是整數(shù),因此可得 。
得證「倍數(shù)/約數(shù)關(guān)系」具有傳遞性。
最后
巨劃算的 LeetCode 會(huì)員優(yōu)惠通道目前仍可用 ~
使用福利優(yōu)惠通道 leetcode.cn/premium/?promoChannel=acoier,年度會(huì)員 有效期額外增加兩個(gè)月,季度會(huì)員 有效期額外增加兩周,更有超大額專屬 🧧 和實(shí)物 🎁 福利每月發(fā)放。
我是宮水三葉,每天都會(huì)分享算法知識(shí),并和大家聊聊近期的所見所聞。
歡迎關(guān)注,明天見。
更多更全更熱門的「筆試/面試」相關(guān)資料可訪問排版精美的 合集新基地 🎉🎉