node實現文件屬性批量修改(時間屬性)

前言

在默認情況下,一個文件的創建時間和修改時間是系統自己設定的,我們不能修改該的。但我們有時為了某種特殊需要,為了不讓別人一眼看出文件已經給修改了,我們又需要修改文件的創建時間和修改時間。那麼如何修改文件夾時間,如何修改文件的創建時間,如何批量修改文件的創建時間、修改時間和訪問時間呢?別著急,接下來就帶你自己修改他們。所以,閑話不多說啦,開始寫我們的代碼啦~~

ps:小工具推薦NewFileTime,以上簡述摘抄於NewFileTime

簡單的搭建一下

  • 新建一個 files 目錄

  • 初始化一個node項目工程

    npm init -y
    

看到這裏你會發現,其實我沒有安裝依賴,是因為原生的庫有這個自帶的功能嗎?說是也行,說不是也行。原生的utimes目前支持修改文件的修改時間和訪問時間,不支持修改文件的創建時間,所以我們需要藉助一個第三方庫來修改。

為什麼不直接安裝這個第三方庫呢?

因為這個庫有些許特殊,分兩種情況,一個是低版本Node可以直接安裝,在我本機的Node13上運行則會失敗。具體原因嘛,可以看看下方的鏈接

ps: 原因 + 解決方案

所以,在低版本的Node我們可以直接npm install @ronomon/utimes,而在版本相對較高的則需要npm i https://github.com/Jule-/utimes.git#napi-migration啦

這裏也提一嘴,如果@ronomon/utimes安裝失敗的話,是因為這些原生Node拓展是需要編譯的,所以我們可能需要安裝windows-build-tools,即以管理員身份啟動PowerShell並運行:

npm install --global windows-build-tools

安裝完依賴之後就可以正式寫我們的代碼啦,其實這個代碼相對簡單,就是直接調用它的api就好了。

簡單的使用一下

  • 新建一個test-files文件夾

  • 在test-files文件夾新建1.txt文件供我們測試

  • 編寫如下代碼:

    // 導入 utimes
    const { utimes } = require("@ronomon/utimes");
    utimes(
      "./test-files/1.txt",
      // 創建時間
      +new Date("2010/01/01"),
      // 修改時間
      +new Date("2010/01/02"),
      // 訪問時間
      +new Date("2010/01/03"),
      (err) => {
        //  修改成功的回調
        console.log(`success`);
      }
    );
    
  • 運行代碼,node app.js,是都發現日期發生了改變呢?

看到這裏你以為是不是寫完了,其實也差不多了 ,不過我當然不會讓你收穫這麼少的,至少我們可以看看我們這個最最最簡單的例子的缺點,比如代碼沒有Promise化,那麼我們就封裝一下utimes

/**
 *
 * @param {String} path => 路徑
 * @param {Number} btime => 創建時間,不傳即不修改
 * @param {Number} mtime => 修改時間,不傳即不修改
 * @param {Number} atime => 訪問時間,不傳即不修改
 */
const utimesPromise = (path, btime, mtime, atime) => {
  return new Promise((resolve, reject) => {
    utimes(path, btime, mtime, atime, (err) => (err ? reject(err) : resolve()));
  });
};

當然- -,因為我們使用的是Node,所以我們不需要常規的用new Promise封裝,可以直接使用內置的util這個工具中的promisify方法封裝即可

util.promisify 是在 node.js 8.x 版本中新增的一個工具,用於將老式的 Error first callback 轉換為 Promise 對象,讓老項目改造變得更為輕鬆。在官方推出這個工具之前,民間已經有很多類似的工具了,比如 es6-promisify、thenify、bluebird.promisify。以及很多其他優秀的工具,都是實現了這樣的功能,幫助我們在處理老項目的時候,不必費神將各種代碼使用 Promise 再重新實現一遍。

所以,我們的封裝又變得更加簡單了,代碼如下:

const { promisify } = require("util");
const utimesPromise = promisify(utimes);

之前的代碼就可以改寫成之前我們那樣的自執行Async Function了,代碼如下:

// ...
(async () => {
  await utimesPromise(
    "./test-files/1.txt",
    // 創建事件
    +new Date("2010/01/01"),
    // 修改時間
    +new Date("2010/01/02"),
    // 訪問時間
    +new Date("2010/01/03")
  );
})();

寫到這裏,你會發現其實我們根本沒有做批量修改,是因為有了之前的經驗,我們可以直接通過glob這個工具獲取所有的路徑,根本不要我們操心,寫起來也十分簡單,所以我打算最後再來寫

  • 安裝glob

    npm i glob -S
    
  • 多建幾個文件用於測試我們的代碼

    得出下面列表:

  • 修改我們的代碼:

    const { utimes } = require("@ronomon/utimes");
    const glob = require("glob");
    const { promisify } = require("util");
    
    /**
     *
     * @param {String} path => 路徑
     * @param {Number} btime => 創建時間,不傳即不修改
     * @param {Number} mtime => 修改時間,不傳即不修改
     * @param {Number} atime => 訪問時間,不傳即不修改
     */
    const utimesPromise = promisify(utimes);
    
    (async () => {
      const paths = glob.sync("./test-files/**");
      const len = paths.length;
      for (let i = 0; i < len; i++) {
        await utimesPromise(
          paths[i],
          +new Date("2010/01/01"),
          +new Date("2010/01/02"),
          +new Date("2010/01/04")
        );
      }
    })();
    
  • 得出結果

這樣子就遞歸了我們所有的文件夾跟子文件了進行修改了,本來想着在加載名字修改的,但苦於- -沒有界面,篇幅也過長,就留着過幾天再寫了。

gitee 地址,github 地址

最後

感謝各位觀眾老爺的觀看 O(∩_∩)O 希望你能有所收穫

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

【遞歸題】正確的打開方式,面試官聽了都說精闢

前言

遞歸,是一個非常重要的概念,也是面試中非常喜歡考的。因為它不但能考察一個程序員的算法功底,還能很好的考察對時間空間複雜度的理解和分析。

本文只講一題,也是幾乎所有算法書講遞歸的第一題,但力爭講出花來,在這裏分享四點不一樣的角度,讓你有不同的收穫。

  • 時空複雜度的詳細分析
  • 識別並簡化遞歸過程中的重複運算
  • 披上羊皮的狼
  • 適當炫技助我拿到第一份工作

算法思路

大家都知道,一個方法自己調用自己就是遞歸,沒錯,但這隻是理解遞歸的最表層的理解。

那麼遞歸的實質是什麼?

答:遞歸的實質是能夠把一個大問題分解成比它小點的問題,然後我們拿到了小問題的解,就可以用小問題的解去構造大問題的解。

那小問題的解是如何得到的?

答:用再小一號的問題的解構造出來的,小到不能再小的時候就是到了零號問題的時候,也就是 base case 了。

那麼總結一下遞歸的三個步驟:

Base case:就是遞歸的零號問題,也是遞歸的終點,走到最小的那個問題,能夠直接給出結果,不必再往下走了,否則,就會成死循環;

拆解:每一層的問題都要比上一層的小,不斷縮小問題的 size,才能從大到小到 base case;

組合:得到了小問題的解,還要知道如何才能構造出大問題的解。

所以每道遞歸題,我們按照這三個步驟來分析,把這三個問題搞清楚,代碼就很容易寫了。

斐波那契數列

這題雖是老生常談了,但相信我這裏分享的一定會讓你有其他收穫。

題目描述

斐波那契數列是一位意大利的數學家,他閑着沒事去研究兔子繁殖的過程,研究着就發現,可以寫成這麼一個序列:1,1,2,3,5,8,13,21…也就是每個數等於它前兩個數之和。那麼給你第 n 個數,問 F(n) 是多少。

解析

用數學公式表示很簡單:

f(n) = f(n-1) + f(n-2)

代碼也很簡單,用我們剛總結的三步:

  • base case: f(0) = 0, f(1) = 1.
  • 分解:f(n-1), f(n-2)
  • 組合:f(n) = f(n-1) + f(n-2)

那麼寫出來就是:

class Solution {
    public int fib(int N) {
        if (N == 0) {
            return 0;
        } else if (N == 1) {
            return 1;
        }
        return fib(N-1) + fib(N-2);
    }
}

但是這種解法 Leetcode 給出的速度經驗只比 15% 的答案快,因為,它的時間複雜度實在是太高了!

過程分析

那這就是我想分享的第一點,如何去分析遞歸的過程。

首先我們把這顆 Recursion Tree 畫出來,比如我們把 F(5) 的遞歸樹畫出來:

那實際的執行路線是怎樣的?

首先是沿着最左邊這條線一路到底:F(5) → F(4) → F(3) → F(2) → F(1),好了終於有個 base case 可以返回 F(1) = 1 了,然後返回到 F(2) 這一層,再往下走,就是 F(0),又觸底反彈,回到 F(2),得到 F(2) = 1+0 =1 的結果,把這個結果返回給 F(3),然後再到 F(1),拿到結果后再返回 F(3) 得到 F(3) = 左 + 右 = 2,再把這個結果返上去…

這種方式本質上是由我們計算機的馮諾伊曼體系造就的,目前一個 CPU 一個核在某一時間只能執行一條指令,所以不能 F(3) 和 F(4) 一起進行了,一定是先執行了 F(4) (本代碼把 fib(N-1) 放在前面),再去執行 F(3).

我們在 IDE 里 debug 就可以看到棧裏面的情況:這裏確實是先走的最左邊這條線路,一共有 5 層,然後再一層層往上返回。

時間複雜度分析

如何評價一個算法的好壞?

很多問題都有多種解法,畢竟條條大路通羅馬。但如何評價每種方法的優劣,我們一般是用大 O 表達式來衡量時間和空間複雜度。

時間複雜度:隨着自變量的增長,所需時間的增長情況。

這裏大 O 表示的是一個算法在 worst case 的表現情況,這就是我們最關心的,不然春運搶車票的時候系統 hold 不住了,你跟我說這個算法很優秀?

當然還有其他衡量時間和空間的方式,比如

Theta: 描述的是 tight bound Omega(n):
這個描述的是 best case,最好的情況,沒啥意義

這也給我們了些許啟發,不要說你平時表現有多好,沒有意義;面試衡量的是你在 worst case 的水平;不要說面試沒有發揮出你的真實水平,扎心的是那就是我們的真實水平。

那對於這個題來說,時間複雜度是多少呢?

答:因為我們每個節點都走了一遍,所以是把所有節點的時間加起來就是總的時間。

在這裏,我們在每個節點上做的事情就是相加求和,是 O(1) 的操作,且每個節點的時間都是一樣的,所以:

總時間 = 節點個數 * 每個節點的時間

那就變成了求節點個數的數學題:

在 N = 5 時,

最上面一層有1個節點,
第二層 2 個,
第三層 4 個,
第四層 8 個,
第五層 16 個,如果填滿的話,想象成一顆很大的樹:)

這裏就不要在意這個沒填滿的地方了,肯定是會有差這麼幾個 node,但是大 O 表達的時間複雜度我們剛說過了,求的是 worst case.

那麼總的節點數就是:
1 + 2 + 4 + 8 + 16

這就是一個等比數列求和了,當然你可以用數學公式來算,但還有個小技巧可以幫助你快速計算:

其實前面每一層的節點相加起來的個數都不會超過最後一層的節點的個數,總的節點數最多也就是最後一層節點數 * 2,然後在大 O 的時間複雜度裏面常數項也是無所謂的,所以這個總的時間複雜度就是:

最後一層節點的個數:2^n

空間複雜度分析

一般書上寫的空間複雜度是指:

算法運行期間所需佔用的所有內存空間

但是在公司里大家常用的,也是面試時問的指的是
Auxiliary space complexity:

運行算法時所需佔用的額外空間。

舉例說明區別:比如結果讓你輸出一個長度為 n 的數組,那麼這 O(n) 的空間是不算在算法的空間複雜度里的,因為這個空間是跑不掉的,不是取決於你的算法的。

那空間複雜度怎麼分析呢?

我們剛剛說到了馮諾伊曼體系,從圖中也很容易看出來,是最左邊這條路線佔用 stack 的空間最多,一直不斷的壓棧,也就是從 5 到 4 到 3 到 2 一直壓到 1,才到 base case 返回,每個節點佔用的空間複雜度是 O(1),所以加起來總的空間複雜度就是 O(n).

優化算法

那我們就想了,為什麼這麼一個簡簡單單的運算竟然要指數級的時間複雜度?到底是為什麼讓時間如此之大。

那也不難看出來,在這棵 Recursion Tree 里,有太多的重複計算了。

比如一個 F(2) 在這裏都被計算了 3 次,F(3) 被計算了 2 次,每次還都要再重新算,這不就是狗熊掰棒子嗎,真的是一把辛酸淚。

那找到了原因之後,為了解決這種重複計算,計算機採用的方法其實和我們人類是一樣的:記筆記。

對很多職業來說,比如醫生、律師、以及我們工程師,為什麼越老經驗值錢?因為我們見得多積累的多,下次再遇到類似的問題時,能夠很快的給出解決方案,哪怕一時解決不了,也避免了一些盲目的試錯,我們會站在過去的高度不斷進步,而不是每次都從零開始。

回到優化算法上來,那計算機如何記筆記呢?

我們要想求 F(n),無非也就是要
記錄 F(0) ~ F(n-1) 的值,
那選取一個合適的數據結構來存儲就好了。

那這裏很明顯了,用一個數組來存:

Index 0 1 2 3 4 5
F(n) 0 1 1 2 3 5

那有了這個 cheat sheet,我們就可以從前到后得到結果了,這樣每一個點就只算了一遍,用一個 for loop 就可以寫出來,代碼也非常簡單。

class Solution {
    public int fib(int N) {
        if (N == 0) {
            return 0;
        }
        if (N== 1) {
            return 1;
        }
        int[] notes = new int[N+1];
        notes[0] = 0;
        notes[1] = 1;
        for(int i = 2; i <= N; i++) {
            notes[i] = notes[i-1] + notes[i-2];
        }
        return notes[N];
    }
}

這個速度就是 100% 了~

但是我們可以看到,空間應該還有優化的餘地。

那仔細想想,其實我們記筆記的時候需要記錄這麼多嗎?需要從幼兒園到小學到初中到高中的筆記都留着嗎?

那其實每項的計算只取決於它前面的兩項,所以只用保留這兩個就好了。

那我們可以用一個長度為 2 的數組來計算,或者就用 2 個變量。

更新代碼:

class Solution {
    public int fib(int N) {
        int a = 0;
        int b = 1;
        if(N == 0) {
            return a;
        }
        if(N == 1) {
            return b;
        }
        for(int i = 2; i <= N; i++) {
            int tmp = a + b;
            a = b;
            b = tmp;
        }
        return b;
    }
}

這樣我們就把空間複雜度優化到了 O(1),時間複雜度和用數組記錄一樣都是 O(n).

這種方法其實就是動態規劃 Dynamic Programming,寫出來的代碼非常簡單。

那我們比較一下 Recursion 和 DP:

Recursion 是從大到小,層層分解,直到 base case 分解不了了再組合返回上去;
DP 是從小到大,記好筆記,不斷進步。
也就是 Recursion + Cache = DP

如何記錄這個筆記,如何高效的記筆記,這是 DP 的難點。

有人說 DP 是拿空間換時間,但我不這麼認為,這道題就是一個很好的例證。

在用遞歸解題時,我們可以看到,空間是 O(n) 在棧上的,但是用 DP 我們可以把空間優化到 O(1),DP 可以做到時間空間的雙重優化。

其實呢,斐波那契數列在現實生活中也有很多應用。

比如在我司以及很多大公司里,每個任務要給分值,1分表示大概需要花1天時間完成,然後分值只有>1,2,3,5,8這5種,(如果有大於8分的任務,就需要把它 break down 成8分以內的,以便大家在>兩周內能完成。)
因為任務是永遠做不完的而每個人的時間是有限的,所以每次小組會開會,挑出最重要的任務讓大家來>做,然後每個人根據自己的 available 的天數去 pick up 相應的任務。

那有同學可能會想,這題這麼簡單,這都 2020 年了,面試還會考么?

答:真的會。

只是不能以這麼直白的方式給你了。

比如很有名的爬樓梯問題:

一個 N 階的樓梯,每次能走一層或者兩層,問一共有多少種走法。

這個題這麼想:

站在當前位置,只能是從前一層,或者前兩層上來的,所以 f(n) = f(n-1) + f(n-2).

這題是我當年面試時真實被問的,那時我還在寫 python,為了炫技,還用了lambda function:

f = lambda n: 1 if n in (1, 2) else f(n-1) + f(n-2)

遞歸的寫法時間複雜度太高,所以又寫了一個 for loop 的版本

def fib(n)
  a, b = 1, 1
  for i in range(n-1):
    a, b = b, a+b
  return a 

然後還寫了個 caching 的方法:

def cache(f):
    memo = {}
    def helper(x):
        if x not in memo:
            memo[x] = f(x)
        return memo[x]
    return helper
@cache
def fibR(n):
    if n==1 or n==2: return 1
    return fibR(n-1) + fibR(n-2)

還順便和面試官聊了下 tail recursion:

tail recursion 尾遞歸:就是遞歸的這句話是整個方法的最後一句話。

那這個有什麼特別之處呢?

尾遞歸的特點就是我們可以很容易的把它轉成 iterative 的寫法,當然有些智能的編譯器會自動幫我們做了(不是說顯性的轉化,而是在運行時按照 iterative 的方式去運行,實際消耗的空間是O(1))

那為什麼呢?

因為回來的時候不需要 backtrack,遞歸這裏就是最後一步了,不需要再往上一層返值。

def fib(n, a=0, b=1):
    if n==0: return a
      if n==1: return b
    return fib(n-1, b, a+b)

最終,拿出了我的殺手鐧:lambda and reduce

fibRe = lambda n: reduce(lambda x, n: [x[1], x[0]+x[1]], range(n), [0, 1])

看到面試官滿意的表情后,就開始繼續深入的聊了…

所以說,不要以為它簡單,同一道題可以用七八種方法來解,分析好每個方法的優缺點,引申到你可以引申的地方,展示自己紮實的基本功,這場面試其實就是你 show off 的機會,這樣才能騙過面試官啊~lol

這就是本文的所有內容了,不知道大家看完感受如何?留言告訴我你的感受吧~

點擊在看,鼓勵下我啊!

瞎寫評論,顯得我很紅啊!

轉發轉發轉發,愛她,就送給她!

還想跟我看更多數據結構和算法題的小夥伴們,記得關注我,我是程序零世界,算法就這麼回事。

作者:小齊本齊

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※FB行銷專家,教你從零開始的技巧

一文看懂《最大子序列和問題》

引言

在做KB的基礎DP練習題的時候遇到了最大子序列和的變種問題,突然發現自己以前沒做過解題筆記(現補上)

最大子序列和是一道經典的算法題, leetcode 也有原題《53.maximum-sum-subarray》,今天我們就來徹底攻克它。

題目描述

求取數組中最大連續子序列和,例如給定數組為 A = [1, 3, -2, 4, -5], 則最大連續子序列和為 6,即 1 + 3 +(-2)+ 4 = 6。
去

首先我們來明確一下題意。

  • 題目說的子數組是連續的
  • 題目只需要求和,不需要返回子數組的具體位置。
  • 數組中的元素是整數,但是可能是正數,負數和 0。
  • 子序列的最小長度為 1。

比如:

  • 對於數組 [1, -2, 3, 5, -3, 2], 應該返回 3 + 5 = 8
  • 對於數組 [0, -2, 3, 5, -1, 2], 應該返回 3 + 5 + -1 + 2 = 9
  • 對於數組 [-9, -2, -3, -5, -3], 應該返回 -2

解法一 – 暴力法(超時法)

一般情況下,先從暴力解分析,然後再進行一步步的優化。

思路

我們來試下最直接的方法,就是計算所有的子序列的和,然後取出最大值。
記 Sum[i,….,j]為數組 A 中第 i 個元素到第 j 個元素的和,其中 0 <= i <= j < n,
遍歷所有可能的 Sum[i,….,j] 即可。

我們去枚舉以 0,1,2…n-1 開頭的所有子序列即可,
對於每一個開頭的子序列,我們都去枚舉從當前開始到 n-1 的所有情況。

這種做法的時間複雜度為 O(N^2), 空間複雜度為 O(1)。

代碼

Java:

class MaximumSubarrayPrefixSum {
  public int maxSubArray(int[] nums) {
      int len = nums.length;
      int maxSum = Integer.MIN_VALUE;
      int sum = 0;
      for (int i = 0; i < len; i++) {
        sum = 0;
        for (int j = i; j < len; j++) {
          sum += nums[j];
          maxSum = Math.max(maxSum, sum);
        }
      }
      return maxSum;
  }
}

Python 3:

import sys
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        maxSum = -sys.maxsize
        sum = 0
        for i in range(n):
            sum = 0
            for j in range(i, n):
                sum += nums[j]
                maxSum = max(maxSum, sum)

        return maxSum

空間複雜度非常理想,但是時間複雜度有點高。怎麼優化呢?我們來看下下一個解法。

解法二 – 分治法

思路

我們來分析一下這個問題, 我們先把數組平均分成左右兩部分。

此時有三種情況:

  • 最大子序列全部在數組左部分
  • 最大子序列全部在數組右部分
  • 最大子序列橫跨左右數組

對於前兩種情況,我們相當於將原問題轉化為了規模更小的同樣問題。

對於第三種情況,由於已知循環的起點(即中點),我們只需要進行一次循環,分別找出
左邊和右邊的最大子序列即可。

所以一個思路就是我們每次都對數組分成左右兩部分,然後分別計算上面三種情況的最大子序列和,
取出最大的即可。

舉例說明,如下圖:

這種做法的時間複雜度為 O(N*logN), 空間複雜度為 O(1)。

代碼

Java:

class MaximumSubarrayDivideConquer {
  public int maxSubArrayDividConquer(int[] nums) {
      if (nums == null || nums.length == 0) return 0;
      return helper(nums, 0, nums.length - 1);
    }
    private int helper(int[] nums, int l, int r) {
      if (l > r) return Integer.MIN_VALUE;
      int mid = (l + r) >>> 1;
      int left = helper(nums, l, mid - 1);
      int right = helper(nums, mid + 1, r);
      int leftMaxSum = 0;
      int sum = 0;
      // left surfix maxSum start from index mid - 1 to l
      for (int i = mid - 1; i >= l; i--) {
        sum += nums[i];
        leftMaxSum = Math.max(leftMaxSum, sum);
      }
      int rightMaxSum = 0;
      sum = 0;
      // right prefix maxSum start from index mid + 1 to r
      for (int i = mid + 1; i <= r; i++) {
        sum += nums[i];
        rightMaxSum = Math.max(sum, rightMaxSum);
      }
      // max(left, right, crossSum)
      return Math.max(leftMaxSum + rightMaxSum + nums[mid], Math.max(left, right));
    }
}

Python 3 :

import sys
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        return self.helper(nums, 0, len(nums) - 1)
    def helper(self, nums, l, r):
        if l > r:
            return -sys.maxsize
        mid = (l + r) // 2
        left = self.helper(nums, l, mid - 1)
        right = self.helper(nums, mid + 1, r)
        left_suffix_max_sum = right_prefix_max_sum = 0
        sum = 0
        for i in reversed(range(l, mid)):
            sum += nums[i]
            left_suffix_max_sum = max(left_suffix_max_sum, sum)
        sum = 0
        for i in range(mid + 1, r + 1):
            sum += nums[i]
            right_prefix_max_sum = max(right_prefix_max_sum, sum)
        cross_max_sum = left_suffix_max_sum + right_prefix_max_sum + nums[mid]
        return max(cross_max_sum, left, right)

解法三 – 動態規劃

思路

我們來思考一下這個問題, 看能不能將其拆解為規模更小的同樣問題,並且能找出
遞推關係。

我們不妨假設問題 Q(list, i) 表示 list 中以索引 i 結尾的情況下最大子序列和,
那麼原問題就轉化為 Q(list, i), 其中 i = 0,1,2…n-1 中的最大值。

我們繼續來看下遞歸關係,即 Q(list, i)和 Q(list, i – 1)的關係,
即如何根據 Q(list, i – 1) 推導出 Q(list, i)。

如果已知 Q(list, i – 1), 我們可以將問題分為兩種情況,即以索引為 i 的元素終止,
或者只有一個索引為 i 的元素。

  • 如果以索引為 i 的元素終止, 那麼就是 Q(list, i – 1) + list[i]
  • 如果只有一個索引為 i 的元素,那麼就是 list[i]

分析到這裏,遞推關係就很明朗了,即Q(list, i) = Math.max(0, Q(list, i - 1)) + list[i]

舉例說明,如下圖:

這種算法的時間複雜度 O(N), 空間複雜度為 O(1)

代碼

Java:

class MaximumSubarrayDP {
  public int maxSubArray(int[] nums) {
     int currMaxSum = nums[0];
     int maxSum = nums[0];
     for (int i = 1; i < nums.length; i++) {
       currMaxSum = Math.max(currMaxSum + nums[i], nums[i]);
       maxSum = Math.max(maxSum, currMaxSum);
     }
     return maxSum;
  }
}

Python 3:

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        max_sum_ending_curr_index = max_sum = nums[0]
        for i in range(1, n):
            max_sum_ending_curr_index = max(max_sum_ending_curr_index + nums[i], nums[i])
            max_sum = max(max_sum_ending_curr_index, max_sum)

        return max_sum

解法四 – 數學分析

思路

我們來通過數學分析來看一下這個題目。

我們定義函數 S(i) ,它的功能是計算以 0(包括 0)開始加到 i(包括 i)的值。

那麼 S(j) – S(i – 1) 就等於 從 i 開始(包括 i)加到 j(包括 j)的值。

我們進一步分析,實際上我們只需要遍歷一次計算出所有的 S(i), 其中 i 等於 0,1,2….,n-1。
然後我們再減去之前的 S(k),其中 k 等於 0,1,i – 1,中的最小值即可。 因此我們需要
用一個變量來維護這個最小值,還需要一個變量維護最大值。

這種算法的時間複雜度 O(N), 空間複雜度為 O(1)。

其實很多題目,都有這樣的思想, 比如之前的《每日一題 – 電梯問題》。

代碼

Java:

class MaxSumSubarray {
  public int maxSubArray3(int[] nums) {
      int maxSum = nums[0];
      int sum = 0;
      int minSum = 0;
      for (int num : nums) {
        // prefix Sum
        sum += num;
        // update maxSum
        maxSum = Math.max(maxSum, sum - minSum);
        // update minSum
        minSum = Math.min(minSum, sum);
      }
      return maxSum;
  }
}

Python 3:

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        maxSum = nums[0]
        minSum = sum = 0
        for i in range(n):
            sum += nums[i]
            maxSum = max(maxSum, sum - minSum)
            minSum = min(minSum, sum)

        return maxSum

總結

我們使用四種方法解決了《最大子序列和問題》,
並詳細分析了各個解法的思路以及複雜度,相信下次你碰到相同或者類似的問題
的時候也能夠發散思維,做到一題多解,多題一解。

實際上,我們只是求出了最大的和,如果題目進一步要求出最大子序列和的子序列呢?
如果要題目允許不連續呢? 我們又該如何思考和變通?如何將數組改成二維,求解最大矩陣和怎麼計算?
這些問題留給讀者自己來思考。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

※網頁設計最專業,超強功能平台可客製化

C#構造函數 -0028

默認構造函數

聲明基本構造函數的語法就是聲明一個與類同名的方法,但該方法沒有返回類型:

public class MyClass
{
	public MyClass()
	{
	}
	// rest of class definition
}

如果沒有提供任何構造函數,編譯器會在後台生成一個默認的構造函數。默認的構造函數,只能把所有的成員字段初始化為標準的默認值。

但是,如果定義了帶參數的構造函數,編譯器就不會自動提取默認的構造函數。

private或protected構造函數

可以把構造函數定義為private或Protected,這樣就限制不相關的類不能訪問它。

比如定義private,

public class MyNumber
{
	private int _number;
	private MyNumber(int number) // another overload
	{
		_number = number;
	}
}

在外部代碼中,不能使用new關鍵字實例化MyNumber;但可以編寫一個公有靜態屬性或方法,以實例化該類,比如單例模式。

public class Singleton
{
	private static Singleton _instance;
	private int _state;
	private Singleton(int state) => _state = state;
	public static Singleton Instance => _instance ?? (_instance = new Singleton(42));
}

構造函數中調用其他構造函數

class Car
{
	private string _description;
	private uint _nWheels;
	public Car(string description, uint nWheels)
	{
		_description = description;
		_nWheels = nWheels;
	}
	public Car(string description): this(description, 4)
	{
	}
	// ...
}

通過this關鍵字調用另一個構造函數,這種語法稱為構造函數初始化器。this關鍵字調用參數最匹配的那個構造函數。

注意,構造函數初始化器在構造函數的函數體之前執行。如:

var myCar = new Car("Proton Persona");

會先調用有兩個參數的構造函數,然後調用只有一個參數的構造函數。

靜態構造函數

C#可以給類定義無參數的靜態構造函數,這種構造函數只執行一次。

靜態構造函數只能訪問類的靜態成員,不能訪問類的實例成員。

靜態構造函數不能帶任何參數,一個類也只能有一個靜態構造函數。

在C#中,通常在第一次調用類的任何成員之前,執行靜態構造函數。

public enum Color
{
	White,
	Red,
	Green,
	Blue,
	Black
}

  

public static class UserPreferences
{
	public static Color BackColor { get; }
	static UserPreferences()
	{
		DateTime now = DateTime.Now;
		if (now.DayOfWeek == DayOfWeek.Saturday || now.DayOfWeek == DayOfWeek.Sunday)
		{
			BackColor = Color.Green;
		}
		else
		{
			BackColor = Color.Red;
		}
	}
}

  

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

深入理解JVM(③)低延遲的Shenandoah收集器

前言

Shenandoah作為第一款不由Oracle(包括一起的Sun)公司的虛擬機團隊所領導開發的HotSpot垃圾收集器。是只存在於OpenJDK當中的,最初由RedHat公司創建的,在2014年的時候貢獻給了OpenJDK。

與G1相比的優點

從代碼的歷史淵源上來看,Shenandoah收集器更像是G1的下一代繼承者,兩者相似的堆內存布局,在初始標記、併發標記等許多階段的處理思路都高度一致。
但是Shenandoah相比G1還是至少有三個明顯的不同之處。
1、支持併發的整理算法,G1的回收階段是可以多線程并行的,但卻不鞥呢與用戶線程併發。
2、Shenandoah是默認不使用分代收集的,不會有專門的新生代Region或者老年代Region的存在。
3、Shenandoah摒棄了在G1中耗費大量內存和計算資源去維護的記憶集,改用名為“連接矩陣”(Connection Matrix)的全局數據結果來記錄誇Region的引用關係降低了誇代維護的消耗。
Shenandoah收集器的跨代“連接矩陣”示意圖

連接矩陣可以簡單的理解為一張二維表格,如果Region N有對象指向Region M,就在表格的N行M列中打上一個標記,如上圖所示,如果Region 5中的對象Object C引用了Region 3 的Object B,Object B又引用了Region 1 的Object A,那麼連接矩陣就中就會在5行3列、3行1列中打上標記。在回收時通過這張表格就可以得出哪些Region 之間產生了跨代引用。

收集過程

Shenandoah收集器的工作過程大致可以劃分為以下九個階段:

  • 初始標記:與G1一樣,首先標記與GC Roots直接關聯的對象,這個階段仍是“Stop The World”的,但停頓時間與堆大小無關,至於GC Roots的數量相關。
  • 併發標記:與G1一樣,編輯對象圖,標記出全部可達的對象,與用戶線程一起併發,時間長短與堆中存活對象的數量以及對象圖的結構複雜程度有關。
  • 最終標記:與G1一樣,處理剩餘的SATB掃描,並在這個階段統計出回收價值最高的Region,將這些Region構成一組回收集。此階段也會有一小段短暫的停頓。
  • 併發清理:這個階段用於清理那些整個區域內連一個存活對象都沒有找到的Region。
  • 併發回收:這個階段是Shenandoah與之前HotSpot中其他收集器的核心差異。在這個階段,Shenandoah要把回收集裏面的存活對象先複製一份到其他未被使用的Region中。但是有個難點是在移動對象的同時,用戶線程仍然可能不停的對被移動的對象進行讀寫訪問,移動對象之後整個內存中所有指向該對象的引用都還是舊對象的地址,這是很難一瞬間全部改變過來的。對於這個難點,Shenandoah將會通過讀屏障和被稱為“Brooks Pointers”的轉髮指針來解決。
    併發回收階段運行時間的長短取決於回收集的大小。
  • 初始引用更新:併發回收階段複製對象結束后,還需要把堆中所有指向舊對象的引用修正蛋糕複製后的新地址,這個操作稱為引用更新。這個階段就是對這個操作進行初始化的,初始引用更新時間很短,會產生一個非常短暫的停頓。
  • 併發引用更新:真正開始進行引用更新操作,這個階段是與用戶線程一起併發的,時間長短取決於內存中涉及的引用數量的多少。
  • 最終引用更新:解決了堆中的引用更新后,還要修正存在於GC Roots 中的引用。這個階段是Shenandoah的最後一次停頓,時間長短與GC Roots的數量有關。
  • 併發清理:經過併發回收和引用更新之後,整個回收集中所有的Region已再無存活對象,最後再調用一次併發清理過程來回收這些Region 的內存空間,供以後新對象分配使用。

這九個階段的工作過程可能拆的比較瑣碎,只要抓住其中三個最重要的併發節點(併發標記、併發回收、併發引用更新)就好理解Shenandoah的運作過程了。

轉髮指針(Brooks Pointer)

Shenandoah收集器的併發回收的核心是,轉髮指針。
轉髮指針的核心內容就是,在原有對象布局結構的最前面統一增加一個新的引用字段,在正常不處於併發移動的情況下,該引用指向對象自己。
如下圖:

轉髮指針加入后帶來的收益自然是當對象擁有了一份新的副本時,只需要修改一處指針的值,即舊對象上轉髮指針的引用位置,使其指向新對象,便可將所有對該對象的訪問轉發到新的副本上。這樣只要對象的內存仍然存在,未被清理掉,虛擬機內存中所有通過舊引用地址訪問的代碼仍然可用,都會被自動轉發到新對象上繼續工作。
如下圖:

Brooks Pointers 轉髮指針在設計上決定了它是必然會出現多線程競爭問題的。Shenandoah收集器是通過比較交換(Compare And Swap,CAS)操作來保證併發時堆中的訪問正確性的。

總結

1、Shenandoah收集器保證了收集垃圾的低延遲。
2、但是使用了過多的寫屏障,所以導致Shenandoah收集器的弱項很明顯,當數據量大的時候會產生高運行負擔而使得吞吐量下降。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※FB行銷專家,教你從零開始的技巧

Enevate推出電動車5分鐘極速快充電池技術

  鋰離子(Li-ion)電池技術公司Enevate Corporation宣布為電動車(EV)推出HD-Energy技術,僅僅5分鐘高能量密度的極速快充可將行駛里程增加多達390公里,充電60秒行駛里程可增加最多達80公里。這一快速充電技術所帶來的極短的充電時間優於目前所有其他鋰離子電池技術,同時滿足汽車對能量密度、里程和成本的進一步要求。Enevate計劃將其以矽為主材的HD-Energy技術授權給全球的電池和電動車製造商及供應商。   這一創新的極速快充技術打破了電動車普及的重重壁壘。一直以來,由於有限的行駛里程所導致的駕駛「里程焦慮」、充電時間過長以及高成本等原因,電動車始終難以普及。如今, Enevate應用於鎳鈷錳(NCM)電動車電池的突破性矽鋰離子電池技術經測試已可用高達10C的充電速率在5分鐘內充電至75%的電池容量且不會影響到電池的使用壽命。同時,其超過750Wh/L的能量密度不會在行駛里程上打折扣。而傳統石墨電池在極速快充中會出現電池急劇退化的問題。   該5分鐘充電技術讓流通出入型充電站的應用成為可能,電動車駕駛人僅需等待幾分鐘即可完成「充電」,就像出入普通加油站一樣。此外,由於充電時間極短,一些電動車中可以選擇使用更小型的電池,使電動車更加多樣化並且經濟適用。   公司創始人兼首席技術官Benjamin Park博士表示:「Enevate以矽為主材的HD-Energy技術具備的優勢可實現新一代功能,將電動車推向全新水平。該技術支持極速快充,可在很短時間便捷地進行充電,具備有助於延長駕駛里程的更高能量密度,同時具備低溫操作的固有安全優勢,這些使其成為電動車電池的理想之選。」   Enevate的HD-Energy電池技術可在低至零下40°C的溫度下實現安全充放電,並且可在再生煞車期間捕獲更多的能量,從而延長了在寒冷氣候中的行駛里程。Enevate HD-Energy技術具備一個關鍵的內在安全優勢,即在快速充電和在低溫充電時可防止鋰析出,這是傳統石墨鋰離子電池所面臨的一個主要挑戰。   德克薩斯大學奧斯汀分校的鋰離子電池先驅John Goodenough博士對此表示贊同,他說:「Enevate以矽為主材的薄膜陽極和電池是一種極具創新性的方法,在電動車應用中具有很大的實用價值,可有效解決電動車普及所面臨的主要障礙。」   (資訊來源:Enevate;首圖來源:Enevate)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※聚甘新

石油需求觸頂是危言聳聽?能源巨擘不怕電動車威脅

  全球環保意識高漲,歐洲和中國相繼宣布,未來將禁售汽柴油車。不少專家預測,石油需求即將觸頂,但是能源巨擘對此嗤之以鼻,深信石油需求將持續成長,對再生能源投資只有石油的九牛一毛。   路透社8日報導,二十年前英國石油公司(BP)看好再生能源,不只改換商標,還宣布要在十年內斥資80億美元發展綠能。不料此一大膽舉動慘敗收場,BP的太陽能事業被陸廠打到無力招架,美國風力發電事業更連賣都賣不掉。BP學到教訓,再次聚焦石油,其他油商也看在眼裡,對於綠能投資格外謹慎。   證據何在?路透訪調分析顯示,全球前五大油商,包括BP、Total、雪佛龍(Chevron)、艾克森美孚(Exxon Mobil)、荷蘭殼牌(Royal Dutch Shell),投資替代能源都只是輕描淡寫。Wood Mackenzie估計前五大油商每年投資的1,000億美元中,只有3%用於再生能源。雪佛龍執行長John Watson說,目前沒有石油需求觸頂的跡象,未來10~20年,石油需求將持續成長。   能源巨擘信心滿滿,是看準新興市場的石油需求將持續增加。艾克森美孚估計,2040年亞洲的運輸需求將使得燃料需求提高25%。BP也說,全球生產石油中,有1/5用於汽車,如果電動車真的奪取大量市佔,空運、鐵路、卡車運輸仍會拉高石油需求。油商也大力投資天然氣,算準就算電動車起飛,天然氣能用於發電,需求仍會成長。   (本文內容由授權使用。首圖來源:pixabay)  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司“嚨底家”!

※推薦評價好的iphone維修中心

※聚甘新

中國新能源車政策鬆綁,外資獲准持有多數股權

  川普本周亞洲行來到中國,中方頻頻讓利討川普歡心。11月9日在習近平與川普的一場會談上,中國宣布訪寬外資對新能源車的持股限制,美國電動車龍頭特斯拉有望受惠。   中國目前限制外資持有合資公司的股份不得超過50%。但從明年六月起,設點在指定自貿區的電動車或其它類型的新能源車合資公司,外資持股比例將可超過五成門檻。   有意深耕中國市場的特斯拉,可能成為政策鬆綁下的潛在受惠者。華爾街日報日前報導指出,特斯拉擬赴上海設廠,且已與中國政府達成協議,雙方只差細節與宣布時間還未敲定,可能正在等待新政策發布。   另外,福特、安徽眾泰汽車(Anhui Zotye Automobile Co.)11月8日在川普的見證下,宣布兩公司將合資7.56億美元,在中國打造動車廠,雙方持股比為50:50。   (本文內容由授權使用。首圖來源:public domain CC0)  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

※聚甘新

砸錢不手軟,戴姆勒才是特斯拉頭號強敵?

  德國車廠戴姆勒(Daimler)豪擲110億美元,計劃在2020年打造電動車隊,出手之大無人出其右,儼然已成為特斯拉最可畏競爭者。   戴姆勒毫不避諱挑戰特斯拉,該公司九月宣布投資位在美國阿拉巴馬的廠房10億美元,計畫在2020年推出電動SUV,許多媒體認為這是在對特斯拉叫陣。   特斯拉執行長Elon Musk當時並不以為意,甚至推文嘲笑戴姆勒投資規模太小,金額後面少一個零,但沒想到戴姆勒隔一天即透過推特官方帳號宣布,研發下一代電動車經費加碼至100億美元以上,並外加至少10億美元開發電池產品。(BusinessInsider)   除此之外,戴姆勒今年三月還與太陽能面板安裝業者Vivint合作,在加州開展家用電池事業,似乎在模仿特斯拉打造以太陽能為基礎的電動車生態圈。   展望未來,中國可能是戴姆勒與特斯拉的最重要決戰場,因為中國是全球最大汽車市場,且未來準備禁賣汽/柴油車。特斯拉赴上海設廠計畫目前還在籌備階段,而戴姆勒七月已與北京汽車集團合資7.5億美元在中國建立電動車生產據點。   (本文內容由授權使用。首圖來源:public domain CC0)  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※聚甘新

倫敦一些地區要在路燈內安裝充電站,讓電動車充電更方便

 

 

隨著電動車逐漸興起,充電的問題變得越來越受到關注,當使用者在城市中開著電動車時,究竟該去哪裡尋找充電設施?Electrek 報導指出,倫敦一些地區似乎正試著透過在路燈柱內安裝充電站的計畫,來解決這個問題。

在充電的問題上,北歐國家似乎具有「先天」優勢,為了面臨嚴峻的冬季氣溫,他們原先在街道上就廣泛設有協助汽車引擎啟動的加熱器(block heater),因此也能夠運用同樣系統讓電動車能在街道上進行充電。

其他沒有這麼寒冷的地方就不同了,在沒有類似基礎設施的情況下,一些公司正在思考相關方案來解決這個問題;像是特斯拉就推出了城市專用的充電站,雖然因為功率低使得充電速度較慢,但也不失為市區內的一個選擇。

另一方面,位在倫敦的肯辛頓與切爾西區(RBKC)的行政當局則選擇了不同的做法,他們已經和能源供應商OVO Energy 和近期獲得西門子投資的德國充電公司ubitricity 簽約,要在都市中現有的燈柱內安裝充電站。

之所以做出這項決定,當地的交通委員會主席Cllr Gerard Hargreaves 表示,是因為居民的充電需求正隨著電動車持續增長,但多數人都無法在附近街道的停車處找到充電設施,讓電動車的充電變得難以進行。

Hargreaves 認為,透過在復古路燈內設置充電設施,駕駛人在住家附近就可以直接充電,倫敦的空氣汙染問題也得以緩解。「除此之外,在路燈內設置意味著不需要額外的基礎建設,更具成本效益的同時也不會影響市容。」

肯辛頓與切爾西區目前計畫安裝的是ubitricity 提供的「SimpleSockets」充電系統,最大輸出功率為4.6 kW。

OVO 表示,SimpleSockets 將會設立在付費和非付費停車格附近的路燈內,24 小時提供使用,每度電只需15 便士(約台幣6 元),這讓電動車不僅更為方便,花費也將更貼近一般人生活。

雖然SimpleSockets 每度電收取的費用與該區的電費規定相當,但Electrek 報導也指出,用戶必須每月繳納7.99 英鎊(約台幣320 元)的訂閱費,同時向ubitricity 購買199 英鎊(約台幣7,960 元)的電纜,才能使用這項收費標準。

當然用戶也可以選擇不繳納訂閱費,但使用上還是必須花100 多英鎊(約台幣4,000 元)購買使用的電纜,同時每度電的收費也將提高到19 便士(約台幣7.6 元),只是即使如此,也遠比英國國內的其他充電選擇好上許多。

ubitricity 目前已經開始在肯辛頓與切爾西區內進行安裝,目標在1 月底前要安裝完成50 個在路燈座內的充電裝置。

(合作媒體:。首圖來源: 臉書)  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※網頁設計最專業,超強功能平台可客製化

※聚甘新