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阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容