[2016/07/06] LeetCode / Java - Day 13 -

来源:互联网 发布:猫王收音机 知乎 编辑:程序博客网 时间:2024/05/29 05:10

137. Single Number II

Given an array of integers, every element appears three times except for one. Find that single one.

Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?

思路:呼,已经记不得是多少次遇见这种题目了。Discuss区有大神给出了通解:(以下就简单翻译意思)

this kind of question the key idea is design a counter that record state. the problem can be every one occurs K times except one occurs M times. for this question, K =3 ,M = 1(or 2) .
so to represent 3 state, we need two bit. let say it is a and b, and c is the incoming bit.
then we can design a table to implement the state move.

这种类型的问题,解决的关键就是创立一个储存状态的计数数组。这个问题可以扩展为 每个数字都出现K次,除了一个出现M次。在这个问题中,K=3,M=1/2。

为了代表三种状态,需要2个bit(2^1<3<2^2)。这两个比特分别是a和b,c是num[i]

接下来我们设计一个状态转移表。

current   incoming  nexta b            c    a b0 0            0    0 00 1            0    0 11 0            0    1 00 0            1    0 10 1            1    1 01 0            1    0 0

like circuit design, we can find out what the next state will be with the incoming bit.( we only need find the ones)

就像数电的状态转移表(计算机相关专业应该都学过数电吧hhh,就不解释了)一样,我们可以找出num[i]输入后的下一个状态。

为了计算a,我们可以写出转移表达式:
then we have for a to be 1, we have

    current   incoming  next    a b            c    a b    1 0            0    1 0    0 1            1    1 0

and this is can be represented by

a=a&~b&~c + ~a&b&c

and b can do the same we , and we find that

同样,b可以表示为:

b= ~a&b&~c+~a&~b&c

and this is the final formula of a and b and just one of the result set, because for different state move table definition, we can generate different formulas, and this one is may not the most optimised. as you may see other's answer that have a much simple formula, and that formula also corresponding to specific state move table. (if you like ,you can reverse their formula to a state move table, just using the same way but reversely)

这是a和b的计算公式之一,因为不同的状态转移定义会产生不同的公式。这个公式不一定是最好的。

for this questions we need to find the except one
as the question don't say if the one appears one time or two time ,

这个问题中我们要找出那个出现了一次或两次的,也就是说 a 和 b要转化成:
so for ab both

01 10 => 100 => 0

we should return a|b; 我们要返回a | b的值(根据上面的表达式)。
this is the key idea , we can design any based counter and find the occurs any times except one .

    public int singleNumber(int[] nums) {        int a=0,b=0;        for(int c: nums){        int temp = a&~b&~c | ~a&b&c;        b = ~a&b&~c | ~a&~b&c;        a = temp;        }        return a|b;    }

198. House Robber

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

思路:动态规划动态规划~ 每一步做一个决策,是选择和自己隔两个的数,还是隔三个的数咧?

错误示范:

(其实本质思想是一样的,但是代码真的太冗余了,还递归。。。。)

public class Solution {    public int rob(int[] nums) {        int n = nums.length;        if(n==0) return 0;        if(n==1) return nums[0];        if(n==2) return Math.max(nums[0], nums[1]);                return robn(nums, 0);    }        public int robn(int[] nums, int begin){    int len =  nums.length-begin;    if(len==1) return nums[begin];    else if(len==2) return nums[begin]>nums[begin+1]? nums[begin]:nums[begin+1];            int s1=nums[begin],s2=nums[begin+1];    if(begin+2<nums.length)    s1 = nums[begin] + robn(nums, begin+2);    if(begin+3<nums.length)    s2 = nums[begin+1] + robn(nums, begin+3);    return s1>s2?s1:s2;        }}
正确示范:

public class Solution {    public int rob(int[] nums) {        int n = nums.length;        if(n==0) return 0;        if(n==1) return nums[0];                int[] max = new int[n];        max[0] = nums[0];        max[1] = Math.max(nums[0], nums[1]);                for(int i=2;i<nums.length;i++){        max[i] = Math.max(max[i-2]+nums[i], max[i-1]);        }        return max[n-1];    }}


213. House Robber II

After robbing those houses on that street, the thief has found himself a new place for his thievery so that he will not get too much attention. This time, all houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, the security system for these houses remain the same as for those in the previous street.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

思路:唔,和上一题的区别,就是首尾相连了。我的想法是,我们不如给尾巴加上一个为-num[0]的街区?这样是不是就可以按原来的方法做了呢?然后我就发现我真是mdzz。既然首和尾不能共存,那就砍掉首或尾,然后看哪个大不就好了嘛。

public class Solution {    public int rob(int[] nums) {        int n = nums.length;        if(n==0) return 0;        if(n==1) return nums[0];                return Math.max(robc(nums, 0, n-1), robc(nums, 1, n));    }    public int robc(int[] nums, int begin, int end){        int n = end-begin;        if(n==0) return 0;        if(n==1) return nums[begin];        int[] max = new int[n];        max[0] = nums[begin];        max[1] = Math.max(nums[begin], nums[begin+1]);                for(int i=begin+2;i<end;i++){        max[i-begin] = Math.max(max[i-begin-2]+nums[i], max[i-begin-1]);        }        return max[n-1];    }}

337. House Robber III

The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the "root." Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that "all houses in this place forms a binary tree". It will automatically contact the police if two directly-linked houses were broken into on the same night.

Determine the maximum amount of money the thief can rob tonight without alerting the police.

Example 1:

     3    / \   2   3    \   \      3   1
Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.

Example 2:

     3    / \   4   5  / \   \  1   3   1
Maximum amount of money the thief can rob = 4 + 5 = 9.

思路:我得称赞,真是好题好思路。先是一个常规DP问题的求解,然后扩充到circle,接下来是structure。接下来我就不bb了,直接看大神的解题思路。我只能想到Step 1,哈哈哈,也就是1186ms。。。这个时间和优化的差的也太多了。。

Step I -- Think naively

At first glance, the problem exhibits the feature of "optimal substructure": if we want to "rob" maximum amount of money from current binary tree (rooted at "root"), we surely hope that we can do the same to its left and right subtrees.

So going along this line, let's define the function rob(root) which will return the maximum amount of money that we can rob for the binary tree rooted at "root"; the key now is to construct the solution to the original problem from solutions to its subproblems, i.e., how to get rob(root) from rob(root.left), rob(root.right), ... etc.

Apparently the analyses above suggest a recursive solution. And for recursion, it's always worthwhile to figure out the following two properties:

  1. Termination condition: when do we know the answer to rob(root) without any calculation? Of course when the tree is empty -- we've got nothing to rob so the amount of money is zero.

  2. Recurrence relation: i.e., how to get rob(root) from rob(root.left), rob(root.right), ... etc. From the point of view of the tree root, there are only two scenarios at the end: "root" is robbed or is not. If it is, due to the constraint that "we cannot rob any two directly-linked houses", the next level of subtrees that are available would be the four "grandchild-subtrees" (root.left.left, root.left.right, root.right.left, root.right.right). However if root is not robbed, the next level of available subtrees would just be the two "child-subtrees" (root.left, root.right). We only need to choose the scenario which yields the larger amount of money.

嗯,大概意思就是,你要获得maxProfit(root),往前推一步,对于它的子树也是一样的,分别考虑左子树和右子树的最大收益情况(类似前面的回溯递归)。两种情况,要么你的收益包括当前root,要么不包括。

Here is the program for the ideas above:

public int rob(TreeNode root) {    if (root == null) {        return 0;    }        int val = 0;        if (root.left != null) {        val += rob(root.left.left) + rob(root.left.right);    }        if (root.right != null) {        val += rob(root.right.left) + rob(root.right.right);    }        return Math.max(val + root.val, rob(root.left) + rob(root.right));}

However the solution runs very slow (1186 ms) and barely got accepted.

Step II -- Think one step further

In step I, we only considered the aspect of "optimal substructure", but think little about the possibilities of overlapping of the subproblems. For example, to obtain rob(root), we need rob(root.left), rob(root.right), rob(root.left.left), rob(root.left.right), rob(root.right.left), rob(root.right.right); but to get rob(root.left), we also needrob(root.left.left), rob(root.left.right), similarly for rob(root.right). The naive solution above computed these subproblems repeatedly, which resulted in bad time performance. Now if you recall the two conditions for dynamic programming: "optimal substructure" + "overlapping of subproblems", we actually have a DP problem. A naive way to implement DP here is to use a hash map to record the results for visited subtrees.

这个就是说,你在获得root.left & root.right的时候,其实已经在root中计算过了,造成了大量的重复计算。而现在,用一个hashmap来储存已经计算过的root的maxProfit值,这样就不需要重复遍历。

And here is the improved solution:

public int rob(TreeNode root) {    Map<TreeNode, Integer> map = new HashMap<>();    return robSub(root, map);}private int robSub(TreeNode root, Map<TreeNode, Integer> map) {    if (root == null) return 0;    if (map.containsKey(root)) return map.get(root);        int val = 0;        if (root.left != null) {        val += robSub(root.left.left, map) + robSub(root.left.right, map);    }        if (root.right != null) {        val += robSub(root.right.left, map) + robSub(root.right.right, map);    }        val = Math.max(val + root.val, robSub(root.left, map) + robSub(root.right, map));    map.put(root, val);        return val;}

The runtime is sharply reduced to 9ms, at the expense of O(n) space cost (n is the total number of nodes; stack cost for recursion is not counted).

Step III -- Think one step back

In step I, we defined our problem as rob(root), which will yield the maximum amount of money that can be robbed of the binary tree rooted at "root". This leads to the DP problem summarized in step II.

Now let's take one step back and ask why do we have overlapping subproblems? If you trace all the way back to the beginning, you'll find the answer lies in the way how we have defined rob(root). As I mentioned, for each tree root, there are two scenarios: it is robbed or is not. rob(root) does not distinguish between these two cases, so "information is lost as the recursion goes deeper and deeper", which resulted in repeated subproblems.

If we were able to maintain the information about the two scenarios for each tree root, let's see how it plays out. Redefine rob(root)as a new function which will return an array of two elements, the first element of which denotes the maximum amount of money that can be robbed if "root" is not robbed, while the second element signifies the maximum amount of money robbed if root is robbed.

Let's relate rob(root) to rob(root.left) and rob(root.right), etc. For the 1st element of rob(root), we only need to sum up the larger elements of rob(root.left) and rob(root.right), respectively, since root is not robbed and we are free to rob the left and right subtrees. For the 2nd element of rob(root), however, we only need to add up the 1st elements of rob(root.left) androb(root.right), respectively, plus the value robbed from "root" itself, since in this case it's guaranteed that we cannot rob the nodes of root.left and root.right.

我们为什么会重复计算呢,是因为随着二叉树的深入,之前的计算没有得到有效保存和利用。所以,当我们区分对于当前root的两种情况的时候,需要保存两个返回值,一个是用了root的值可以获得的最大值,还有一个就是没有用root值可以获得的最大值。

As you can see, by keeping track of the information of both scenarios, we decoupled the subproblems and the solution essentially boiled down to a greedy one. Here is the program:

public int rob(TreeNode root) {int[] res = robSub(root);    return Math.max(res[0], res[1]);}private int[] robSub(TreeNode root) {    if (root == null) {    return new int[2];    }        int[] left = robSub(root.left);    int[] right = robSub(root.right);        int[] res = new int[2];    res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);    res[1] = root.val + left[0] + right[0];        return res;}


0 0
原创粉丝点击