tx_orderByFrequency_21~40

292. Nim 游戲

你和你的朋友,兩個人一起玩 Nim 游戲:桌子上有一堆石頭,每次你們輪流拿掉 1 - 3 塊石頭。 拿掉最后一塊石頭的人就是獲勝者。你作為先手。

你們是聰明人,每一步都是最優解。 編寫一個函數,來判斷你是否可以在給定石頭數量的情況下贏得游戲。

輸入: 4
輸出: false
解釋: 如果堆中有 4 塊石頭,那么你永遠不會贏得比賽;
因為無論你拿走 1 塊、2 塊 還是 3 塊石頭,最后一塊石頭總是會被你的朋友拿走。

思路:我該怎么拿。
我如果后手拿,
第一次先手拿完之后,我拿到剩余石頭數目為4的倍數,然后不管對面怎么拿,我都拿4-X;
我如果先手拿,
如果是已經4的倍數,不管怎么拿,對面都會拿4-X;那么最后一定對面贏,我贏不了。
如果不是4的倍數,我先拿到剩余只是4的倍數,無論對面怎么拿,我都拿4-X;我穩贏。

因此,先手拿 概率是75% 后手是1/4;

代碼:因為4是2的2次方,用與操作代替%; hash&(n-1) = hash %n;(HashMap源碼)

class Solution {
    public boolean canWinNim(int n) {
        return (n&3)==0?false :true;
    }
}

192. 統計詞頻

寫一個 bash 腳本以統計一個文本文件 words.txt 中每個單詞出現的頻率。

為了簡單起見,你可以假設:

  • words.txt只包括小寫字母和 ' '
  • 每個單詞只由小寫字母組成。
  • 單詞間由一個或多個空格字符分隔。
    假設 words.txt 內容如下:

the day is sunny the the
the sunny is is
你的腳本應當輸出(以詞頻降序排列):

the 4
is 3
sunny 2
day 1

思路:本題是腳本統計題,用awk命令即可。
awk中,可以將字符串當作索引。 $i指的是文本中當前行的第i個字符;
sort默認是從小到大 -r逆序 -k指第幾列 -n為按照數值大小排序

awk '{for(i=1;i<=NF;i++){words[$i]++}} END{for(word in words){print word,words[word]}}' words.txt | sort -k2 -nr

146. LRU緩存機制

運用你所掌握的數據結構,設計和實現一個 LRU (最近最少使用) 緩存機制。它應該支持以下操作: 獲取數據 get 和 寫入數據 put

獲取數據 get(key) - 如果密鑰 (key) 存在于緩存中,則獲取密鑰的值(總是正數),否則返回 -1。
寫入數據 put(key, value) - 如果密鑰不存在,則寫入其數據值。當緩存容量達到上限時,它應該在寫入新數據之前刪除最久未使用的數據值,從而為新的數據值留出空間。

思路: 用linkedHashMap,linkedHashMap構造器如下:
linkedHashMap(int capacity, double loadfactor, boolean accessOrder){

}; 其中,accessOrder表示的是排序方式,默認為false,即:按照插入順序排序。若為true,則按照訪問順序排序從小到大排序。

class LRUCache {

    LinkedHashMap<Integer,Integer> map;
    int size;
    int count;

    public LRUCache(int capacity) {
        map = new LinkedHashMap(capacity,0.75F,true);
        count = 0;
        size = capacity;
    }
    
    public int get(int key) {
        return map.getOrDefault(key,-1);
    }
    
    public void put(int key, int value) {

        if(map.containsKey(key)){
            map.put(key,value);
            return;
        }
        if(count==size){
        // 刪除linkedHashMap中的第一個結點,用iterator迭代器更保險
            Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
            Iterator<Map.Entry<Integer, Integer>> iterator = entries.iterator();
            if(iterator.hasNext()){
                iterator.next();
                iterator.remove();
            }
            count--;
        }
        map.put(key,value);
        count++;
        
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

460. LFU緩存

設計并實現最不經常使用(LFU)緩存的數據結構。它應該支持以下操作:getput

get(key) - 如果鍵存在于緩存中,則獲取鍵的值(總是正數),否則返回 -1。
put(key, value) - 如果鍵不存在,請設置或插入值。當緩存達到其容量時,它應該在插入新項目之前,使最不經常使用的項目無效。在此問題中,當存在平局(即兩個或更多個鍵具有相同使用頻率)時,最近最少使用的鍵將被去除。

思路: 最少~ 想到最小堆。因此采用HashMap存儲key,value;堆存儲順序的方法。
HashMap<Integer,Node>,其中Node包含value信息。
堆存儲Node,根據Node的frequency及時間排序。
Node(int key, int value , int cnt, long time);

class LFUCache {
    private Map<Integer, Node> m;
    private PriorityQueue<Node> q;
    private int size;
    private int capacity;

    public LFUCache(int capacity) {
        size = 0;
        m = new HashMap<>();
        q = new PriorityQueue<>((n1, n2) -> {
            if (n1.getCnt() != n2.getCnt())
                return n1.getCnt() - n2.getCnt();
            return (int) (n1.getTime() - n2.getTime());
        });
        this.capacity = capacity;
    }

    public int get(int key) {
        if (!m.containsKey(key))
            return -1;
        Node node = m.get(key);
        touch(node);
        return node.getValue();
    }

    private void touch(Node node) {
        node.setCnt(node.getCnt() + 1);
        node.setTime(System.nanoTime());
        q.remove(node);
        q.add(node);
    }

    public void put(int key, int value) {
        if (capacity <= 0)
            return;
        if (m.containsKey(key)) {
            Node node = m.get(key);
            node.setValue(value);
            touch(node);
            return;
        }
        if (size == capacity) {
            Node poll = q.poll();
            m.remove(poll.getKey());
            size -= 1;
        }
        Node node = new Node(key, value, 1, System.nanoTime());
        q.add(node);
        m.put(key, node);
        size += 1;
    }
}

class Node {
    private int key;
    private int value;
    private int cnt;
    private long time;
    Node(int key,int value,int cnt, long time){
        this.key = key;
        this.value = value;
        this.cnt = cnt;
        this.time = time;
    }
    public int getKey() {return key; }
    public void setKey(int key) { this.key = key; }
    public int getValue() { return value; }
    public void setValue(int value) { this.value = value; }
    public int getCnt() { return cnt; }
    public void setCnt(int cnt) {this.cnt = cnt; }
    public long getTime() { return time; }
    public void setTime(long time) { this.time = time; }
}

470. 用 Rand7() 實現 Rand10()

已有方法 rand7 可生成 1 到 7 范圍內的均勻隨機整數,試寫一個方法 rand10 生成 1 到 10 范圍內的均勻隨機整數。

不要使用系統的 Math.random() 方法。
思路:
這題重點在生成等概率的隨機數。 7(rand7()-1)可以生成 0,7,14,``` 在此基礎上,加上1~7 就可以填滿1~49;并且每個數字出現的次數都是1. 如果直接rand7()rand7(),中間很多數字調用不到。 如果不是7,如1 直接+1,得到1~-13,中間很多數字重復了。

優化方案:大于40的9個數也是等概率的,因此可以利用這9個數,繼續生成等概率的數
(nums-40-1)*7 ,再用7填滿中間的空

/**
 * The rand7() API is already defined in the parent class SolBase.
 * public int rand7();
 * @return a random integer in the range 1 to 7
 */
class Solution extends SolBase {
    public int rand10() {
        int nums = (rand7()-1)*7+rand7();
        return  nums >40 ? rand10() :nums%10+1;
    }
}

792. 匹配子序列的單詞數

給定字符串 S 和單詞字典 words, 求 words[i] 中是 S 的子序列的單詞個數。
示例:
輸入:
S = "abcde"
words = ["a", "bb", "acd", "ace"]
輸出: 3
解釋: 有三個是 S 的子序列的單詞: "a", "acd", "ace"。</pre>

class Solution {
    public int numMatchingSubseq(String S, String[] words) {
        int count = 0;
        int[] dp = new int[words.length];
        for(int i=0;i<words.length;i++){
            if(i>0&&words[i].equals(words[i-1])){
                if(dp[i-1]==1){
                    dp[i] = dp[i-1];
                    count++;
                }
                continue;
            }
            
            if(juage(S,words[i])){
                dp[i]=1;
                count++;
            }
        }
        return count;
    }

    public boolean juage(String s, String b){
        int sL = s.length();
        int bL = b.length();
        int i=0;
        int j=0;
        while(i!=sL&&j!=bL){
            if(s.charAt(i)==b.charAt(j)){
                i++;
                j++;
            }
            else{
                i++;
            }
        }
        if(j==bL){
            return true;
        }
        return false;
    }
}

741. 摘櫻桃

一個N x N的網格(grid) 代表了一塊櫻桃地,每個格子由以下三種數字的一種來表示:

  • 0 表示這個格子是空的,所以你可以穿過它。
  • 1 表示這個格子里裝著一個櫻桃,你可以摘到櫻桃然后穿過它。
  • -1 表示這個格子里有荊棘,擋著你的路。

你的任務是在遵守下列規則的情況下,盡可能的摘到最多櫻桃:

  • 從位置 (0, 0) 出發,最后到達 (N-1, N-1) ,只能向下或向右走,并且只能穿越有效的格子(即只可以穿過值為0或者1的格子);
  • 當到達 (N-1, N-1) 后,你要繼續走,直到返回到 (0, 0) ,只能向上或向左走,并且只能穿越有效的格子;
  • 當你經過一個格子且這個格子包含一個櫻桃時,你將摘到櫻桃并且這個格子會變成空的(值變為0);
  • 如果在 (0, 0) 和 (N-1, N-1) 之間不存在一條可經過的路徑,則沒有任何一個櫻桃能被摘到。

示例 1:
輸入: grid =
[[0, 1, -1],
[1, 0, -1],
[1, 1, 1]]
輸出: 5
解釋:
玩家從(0,0)點出發,經過了向下走,向下走,向右走,向右走,到達了點(2, 2)。
在這趟單程中,總共摘到了4顆櫻桃,矩陣變成了[[0,1,-1],[0,0,-1],[0,0,0]]。
接著,這名玩家向左走,向上走,向上走,向左走,返回了起始點,又摘到了1顆櫻桃。
在旅程中,總共摘到了5顆櫻桃,這是可以摘到的最大值了。
</pre>

說明:

  • grid 是一個 N * N 的二維數組,N的取值范圍是1 <= N <= 50
  • 每一個 grid[i][j] 都是集合 {-1, 0, 1}其中的一個數。
  • 可以保證起點 grid[0][0] 和終點 grid[N-1][N-1] 的值都不會是 -1。

思路:動態規劃分別求兩次最優解,其答案并不是最優解,只是次優解。必須綜合考慮。

  1. 看成兩個人同時從(0,0)走向(m-1,m-1);坐標分別為(r1,c1)、(r2、c2);
    同時走,即c2 = r1+c1-r2;
  2. 同時走,則dp有四個方向。分別是 下下、右右、下右、右下
    因此用遞歸dp(grid,memo,r1,c1,r2);
class Solution {
    public int cherryPickup(int[][] grid) {
        
        // 求最優解,用dp;求匹配/用backtrace
        int m = grid.length;
        int memo[][][] = new int[m][m][m];
        for (int[][] layer: memo)
            for (int[] row: layer)
                Arrays.fill(row, Integer.MIN_VALUE);
        int res = dp(grid,memo,0,0,0);
        return Math.max(res,0);
    }

    public int dp(int[][] grid,int[][][] memo,int r1,int c1, int r2){
        int c2  =r1+c1-r2;
        if(r1==grid.length||r2==grid.length||c1==grid.length||c2==grid.length||grid[r1][c1]==-1||grid[r2][c2]==-1){
            return -99999;
        }
        if(r1==grid.length-1&&c1==grid.length-1){
            return grid[r1][c1];
        }
        if(memo[r1][c1][r2]!=Integer.MIN_VALUE){
            return memo[r1][c1][r2];
        }
        int ans = 0;
        if(r1!=r2){
            ans = max(dp(grid,memo,r1+1,c1,r2+1),dp(grid,memo,r1+1,c1,r2),dp(grid,memo,r1,c1+1,r2+1),dp(grid,memo,r1,c1+1,r2) ) + grid[r1][c1] + grid[r2][c2];
        }else{
            ans = max(dp(grid,memo,r1+1,c1,r2+1),dp(grid,memo,r1+1,c1,r2),dp(grid,memo,r1,c1+1,r2+1),dp(grid,memo,r1,c1+1,r2)) + grid[r1][c1];
        }
        memo[r1][c1][r2] = ans;
        return ans;
    }

    public int max(int a, int b,int c,int d){
        a = Math.max(a,b);
        a = Math.max(a,c);
        a = Math.max(a,d);
        return a;
    }
}

410. 分割數組的最大值

給定一個非負整數數組和一個整數 m,你需要將這個數組分成 *m *個非空的連續子數組。設計一個算法使得這 *m *個子數組各自和的最大值最小。

注意:
數組長度 *n *滿足以下條件:

  • 1 ≤ n ≤ 1000
  • 1 ≤ m ≤ min(50, n)

示例:
輸入:
nums = [7,2,5,10,8]
m = 2

輸出:
18

解釋:
一共有四種方法將nums分割為2個子數組。
其中最好的方式是將其分為[7,2,5][10,8]
因為此時這兩個子數組各自的和的最大值為18,在所有情況中最小。</pre>

思路:
dp; dp難問題主要是兩種:

  1. 最大的最小值/最小的最大值:這種需要找到dp[i][j]與子問題的動態轉移方程。然后遍歷每一種可能去max/min;如:雞蛋掉落,本題。
  2. 直接dp維度不夠,需要dp[][][];如摘櫻桃,移除盒子。

假設dp[i][j]是nums[0~i]經k次分割得到的最小的最大值。假設最后一刀劃在k上面,那么:
dp[i][j] = max(dp[k][j-1],sum(nums[k~i]));因為需要得到最小的最大值,因此遍歷k;取最小的。

class Solution {
    public int splitArray(int[] nums, int m) {
        int[][] memo = new int[nums.length][m+1];
        int res = Integer.MAX_VALUE;
        res = Math.min(res,dp(nums,nums.length-1,m,memo));
        return res;
    }

    public int dp(int[] nums , int i, int m,int[][] memo){
        if(m>i+1){
            return 0;
        }
        if(m==1){
            return sum(nums,0,i);
        }
        if(memo[i][m]!=0){
            return memo[i][m];
        }
        int minValue = Integer.MAX_VALUE;
        for(int k=0;k<i;k++){
            minValue = Math.min(minValue,Math.max(dp(nums,k,m-1,memo),sum(nums,k+1,i)));
        }  
        memo[i][m] = minValue;
        return minValue;
    }

    public int sum(int[] nums, int i, int j){
        
        int maxValue = 0;
        for(int k=i;k<=j;k++){
            maxValue += nums[k];
        }
        return maxValue;
    }
}

446. 等差數列劃分 II - 子序列

難度困難45 收藏 分享 切換為英文 關注 反饋

如果一個數列至少有三個元素,并且任意兩個相鄰元素之差相同,則稱該數列為等差數列。

例如,以下數列為等差數列:

1 2 3 4 5

示例:

輸入:[2, 4, 6, 8, 10]

輸出:7

解釋:
所有的等差子序列為:
[2,4,6]
[4,6,8]
[6,8,10]
[2,4,6,8]
[4,6,8,10]
[2,4,6,8,10]
[2,6,10]

class Solution {
    public int numberOfArithmeticSlices(int[] A) {
        int n = A.length;
        long ans = 0;
        Map<Integer, Integer>[] cnt = new Map[n];
        for (int i = 0; i < n; i++) {
            cnt[i] = new HashMap<>(i);
            // f[i][diff] 表示以 A[i]為結尾,差為diff的子序列數目(長度為2個以上的子序列數目)
            // f[i][A[i]-A[j]] = f[j][A[i]-A[j]] + 1;
            // 可能出現重復的情況 A[i]-A[j] 重復。即j值重復。
            // 那么,f[i][A[i]-A[j]] += f[j][A[i]-A[j]] + 1;
            // 其中 +1 指增加了[A[i],A[j]];
            // 舉例  若f[j][1] 包含  {2,3} {1,2,3}
            // 那么 f[i][1] 包含 {2,3,4} {1,2,3,4} {3,4} 
            // 用n個HashMap存 f[j][diff]的值;
            // 存的是該A[i]為結尾的數組,Map為 diff:count
            for (int j = 0; j < i; j++) {
                long delta = (long)A[i] - (long)A[j];
                if (delta < Integer.MIN_VALUE || delta > Integer.MAX_VALUE) {
                    continue;
                }
                int diff = (int)delta;
                int sum = cnt[j].getOrDefault(diff, 0);
                int origin = cnt[i].getOrDefault(diff, 0);
                cnt[i].put(diff, origin + sum + 1);
                ans += sum;
            }
        }
        return (int)ans;        
    }
}

4. 尋找兩個有序數組的中位數

給定兩個大小為 m 和 n 的有序數組 nums1nums2

請你找出這兩個有序數組的中位數,并且要求算法的時間復雜度為 O(log(m + n))。

你可以假設 nums1nums2 不會同時為空。

示例 1:

nums1 = [1, 3]
nums2 = [2]

則中位數是 2.0

示例 2:

nums1 = [1, 2]
nums2 = [3, 4]

則中位數是 (2 + 3)/2 = 2.5</pre>

思路:
要求復雜度為O(log(m+n)),則不能單純合并鏈表排序去中間的。
可以看成是在排序數組中取第K小的數。K = (m+n)/2;
將K/2;分別在該nums1和nums2中找到該索引,并比較值。較小值不可能為第K個數。因此舍棄較小值所在數組的1~K/2;如K=7;

image.png
image.png

舍棄之后,K = K-K/2 = 4;即,重新求第4小的數。


image.png

image.png

image.png

image.png
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        //處理任何一個nums為空數組的情況
        if (m == 0) {
            if (n % 2 != 0)
                return 1.0 * nums2[n / 2];
            return (nums2[n / 2] + nums2[n / 2 - 1]) / 2.0;
        }
        if (n == 0) {
            if (m % 2 != 0)
                return 1.0 * nums1[m / 2];
            return (nums1[m / 2] + nums1[m / 2 - 1]) / 2.0;
        }
        int total = m + n;
        if((total&1)==1){
            return findKth(nums1,0,nums2,0,total/2+1);
        }    
        else{
            return (findKth(nums1,0,nums2,0,total/2) + findKth(nums1,0,nums2,0,total/2+1))/2.0;
        }
    }

    public int findKth(int[] nums1, int index1, int[] nums2 ,int index2, int k){

        if(index1>=nums1.length){
            return nums2[index2+k-1];
        }
        if(index2>=nums2.length){
            return nums1[index1+k-1];
        }
        if (k == 1){
            return Math.min(nums1[index1], nums2[index2]);
        }
        int mid1 = Integer.MAX_VALUE;
        int mid2 = Integer.MAX_VALUE;
        if((index1+k/2-1)<nums1.length){
            mid1 = nums1[index1+k/2-1];
        }
        if(index2+k/2-1<nums2.length){
            mid2 = nums2[index2+k/2-1];
        }
        // 如果mid2>mid1 或者mid2為無窮,則nums[index1+k/2]肯定不是第K大的數值。
        if(mid1<mid2){
            return findKth(nums1,index1+k/2,nums2,index2,k-k/2);
        }else{
            return findKth(nums1,index1,nums2,index2+k/2,k-k/2);
        }
    }
}

327. 區間和的個數

給定一個整數數組 nums,返回區間和在 [lower, upper] 之間的個數,包含 lowerupper
區間和 S(i, j) 表示在 nums 中,位置從 ij 的元素之和,包含 ij (ij)。

說明:
最直觀的算法復雜度是 O(n2) ,請在此基礎上優化你的算法。

示例:

輸入: nums = [-2,5,-1], lower = -2, upper = 2,
輸出: 3
解釋: 3個區間分別是: [0,0], [2,2], [0,2],它們表示的和分別為: `-2, -1, 2。

思路:暴力法較簡單,不再討論。
這題類似于求數組中逆序對的個數:歸并排序。
區間和為連續的和;因此,用前綴和來做。
即用sum[i]表示,0~i區間內的nums之和。
那么 區間和表示為 s[j]-s[i] 及 [i,j]的區間和。
s[j] - s[i] >= lower && s[j] - s[i] <= upper 即滿足條件。

如果存在下面這個序列,左邊藍色部分是有序的,右邊黃色部分是有序的,求有多少個答案滿足:

0≤S[i]?S[j]≤4,S[i]∈黃色,S[j]∈藍色


image.png

我們嘗試求解:
首先Left指針指向藍色部分最左端,Lower指針和Upper指針指向黃色部分最左端。


image.png

當S[Lower] - S[Left] < 0就繼續移動Lower;當S[Upper] - S[Left] <= 4就繼續移動Upper之后可以得到:
image.png

此時Upper - Lower就是Left(-1)所對應的個數,這里等于0,因為Upper和Lower之后的值更大,更不可能滿足要求。

接著向左移動Left,但是Upper和Lower并不需要往后移動。
因此Left(0)對應個數為0,繼續移動Left,并相應地移動Upper和Lower得到:
可以得到Left(7)對應個數為2,最后移動Left,Upper和Lower得到:
可以得到Left(9)對應個數為0。
因此最后答案為2,并且我們通過線性的實現就完成了求解過程,因為Left,Upper,Lower都只向右掃描了一遍。
我們回顧一下歸并排序,歸并排序能夠將兩個有序序列在線性時間復雜度下完成合并,我們這里是類似的。

// 歸并的坑: 注意 一: left=right 時(即一個數字時,需要判斷該數字是否滿足條件) 
//注意二: 需要使用long類型。
class Solution {
    int res = 0;
    public int countRangeSum(int[] nums, int lower, int upper) {
        int left = 0;
        int right = nums.length-1;
        if(right<0){
            return 0;
        }
        long[] sum = new long[nums.length];
        long count = 0;
        for(int i=0;i<nums.length;i++){
           count += nums[i];
           sum[i] = count;
        }
        merge(sum,left,right,lower,upper);
        return res;
    }

    public void merge(long[] nums, int left, int right, int lower, int upper){

        if(left>=right){
            if(nums[left]>=lower&&nums[left]<=upper){
                res++;
            }
            return;
        }
        int mid = (left+right)/2;
        merge(nums,left,mid,lower,upper);
        merge(nums,mid+1,right,lower,upper);
        mergeSort(nums,left,mid,right,lower,upper);
    }

    public void mergeSort(long[] nums, int left, int mid, int right, int lower, int upper){
        long[] temp = new long[nums.length];
        int index = left;
        int leftStart= left;
        int leftEnd = mid;
        int rightStart = mid+1;
        int rightEnd = right;
        int lowerIndex = mid+1;
        int upperIndex = mid+1;
        int numElements = rightEnd - leftStart + 1;
        for(int i=leftStart;i<=leftEnd;i++){
            while(lowerIndex<=right&&nums[lowerIndex]-nums[i]<lower){
                lowerIndex++;
            }
            while(upperIndex<=right&&nums[upperIndex]-nums[i]<=upper){
                upperIndex++;
            }
            res += upperIndex-lowerIndex;
        }

        while(leftStart<=leftEnd&&rightStart<=rightEnd){
                        
            if(nums[leftStart]<nums[rightStart]){
                temp[index++] = nums[leftStart++];
            }else{
                temp[index++] = nums[rightStart++];
            }
        }

        if(leftStart>leftEnd){
            while(rightStart<=rightEnd){
                temp[index++] = nums[rightStart++];
            }
        }else{
            while(leftStart<=leftEnd){
                temp[index++] = nums[leftStart++];
            }
        }
          for(int i = 0; i < numElements; i++,rightEnd--) {
            nums[rightEnd] = temp[rightEnd];
          }
    }
}

664. 奇怪的打印機

有臺奇怪的打印機有以下兩個特殊要求:

  1. 打印機每次只能打印同一個字符序列。
  2. 每次可以在任意起始和結束位置打印新字符,并且會覆蓋掉原來已有的字符。

給定一個只包含小寫英文字母的字符串,你的任務是計算這個打印機打印它需要的最少次數。

示例 1:

輸入:* "aaabbb"
輸出: 2
解釋: 首先打印 "aaa" 然后打印 "bbb"。
</pre>

示例 2:

輸入: "aba"
輸出: 2
解釋: 首先打印 "aaa" 然后在第二個位置打印 "b" 覆蓋掉原來的字符 'a'。</pre>

思路:
用dp[i][j]表示從打印s[i~j]的最小使用次數。
其中s[i]肯定是一次打印。那么在s[i~j]中找到與s[i]相同或的s[k],s[i]==s[k],
那么可以用一次打印從i~打印到k。這樣可以減少一次打印s[k]的次數。那么dp[i][k] = dp[i][k-1];
dp[i][j] = dp[i][k-1] + dp[k+1][j];
在所有滿足條件的k中選擇最小的:于是有
dp[i][j] = Math.min(dp[i][k-1]+dp[k+1][j]);
如果不存在滿足條件的k,那么默認就打印一個s[i],即初始化dp[i][j] = 1 + dp[i+1][j];

class Solution {
    int[][] memo;
    public int strangePrinter(String s) {

        // dp[i][j] 打印s[i~j]所需要的次數。
        // s[i] = s[k],則可以從i打印到k,一次打印s[k],且dp[i][k] = dp[i][k-1];
        // dp[i][j] = min(dp[i][k-1]+dp[k+1][j])
        int n = s.length();
        memo = new int[n][n];
        int i=0;
        return dp(s,0,n-1);
    }

    public int dp(String s , int i, int j){
        if(i>j){
            return 0;
        }
        if(memo[i][j]!=0){
            return memo[i][j];
        }
        int res = dp(s, i+1, j) + 1;
        int index = 0;
        for(int k=i+1;k<=j;k++){
            if(s.charAt(i)==s.charAt(k)){
                res = Math.min(res,dp(s,i,k-1)+dp(s,k+1,j));
            }
        }
        memo[i][j] = res;
        return res;
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容