LeetCode:Heap,Sort,Bit Manipulation,Backtracking

15.Heap

先來複習一下stack,queue,heap的特點:
1.stack:FILO(先進後出)
2.queue:FIFO(先進先出)
3.heap:兩個rules,1.the tree is complete,2.parent is always smaller than its two children.

參考differences

1.Design Twitter

題意

未實現顯示前10狀態的功能,如果要實現肯定是可以的,將Twitter寫成一個類,類中包含成員變量時間,然後在getNewsFeed方法中取出每條twitter後再來一次排序,這樣就能得到最新發表的前10條狀態。但這樣也有弊端,如果插入的時間間隔很短,很可能出現兩條狀態時間相同的情況,而維護一個隊列或者其他不依靠絕對時間有序的方法可以避免這樣bug情況。並且這樣實現的方法都太naive了,想想更高級的實現方法吧。初級代碼如下:

public class Twitter {

    //users關係
    Set<User> relations=new HashSet<>();
    /** Initialize your data structure here. */
    public Twitter() {

    }

    /** Compose a new tweet. */
    public void postTweet(int userId, int tweetId) {
        for (User each:
                relations) {
            if(each.id==userId){
                each.twitters.add(tweetId);
                return;
            }
        }
        User user = new User();
        user.id = userId;
        user.twitters.add(tweetId);
        relations.add(user);
    }

    /** Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. */
    public List<Integer> getNewsFeed(int userId) {
        List<User> users = new ArrayList<>();
        for (User each:
                relations) {
            if(each.id==userId){
                users.add(each);
                users.addAll(each.followers);
            }
        }
        List<Integer> news = new ArrayList<>();
        for (User eachUser:
             users) {
            news.addAll(eachUser.twitters);
        }
        for (Integer each:
             news) {
            System.out.println("news:   "+each);
        }
        System.out.println("------");
        return news;
    }

    /** Follower follows a followee. If the operation is invalid, it should be a no-op. */
    public void follow(int followerId, int followeeId) {
        if(followerId==followeeId){
            return;
        }
        User follower = new User();
        follower.id = followerId;

        User followee = new User();
        followee.id = followeeId;

        follower.followers.add(followee);
        followee.followees.add(follower);

        relations.add(follower);
        relations.add(followee);
    }

    /** Follower unfollows a followee. If the operation is invalid, it should be a no-op. */
    public void unfollow(int followerId, int followeeId) {
        if(followerId==followeeId){
            return;
        }
        for (User each:
             relations) {
            if(each.id==followerId){
                for (User ss:
                     each.followers) {
                    if(ss.id==followeeId){
                        each.followers.remove(ss);
                        break;
                    }
                }
            }else if(each.id==followeeId){
                for (User ss:
                        each.followees) {
                    if(ss.id==followerId){
                        each.followees.remove(ss);
                        break;
                    }
                }
            }
        }
    }

}

class User{
    int id=Integer.MIN_VALUE;//用戶標識
    List<Integer> twitters=new ArrayList<>();//發文
    Set<User> followees=new HashSet<>();//被哪些關注了
    Set<User> followers=new HashSet<>();//關注了哪些
}

比我高到不知道哪裏的實現:

public class Twitter {
    //非常聰明,全局的time計數,這樣每次不管哪個user發送twitter,自增1後都能保證是對的順序,同時又避免使用龐大的Date等time類來解決
    //從這裏看出換個角度來想問題的重要性
    private static int timeStamp=0;

    // easy to find if user exist
    //這樣就不必像我實現的那樣遍歷Set,來看id是否匹配了!
    private Map<Integer, User> userMap;

    // Tweet link to next Tweet so that we can save a lot of time
    // when we execute getNewsFeed(userId)
    private class Tweet{
        public int id;
        public int time;
        public Tweet next;

        public Tweet(int id){
            this.id = id;
            //保證順序
            time = timeStamp++;
            next=null;
        }
    }


    // OO design so User can follow, unfollow and post itself
    //用戶調用方法
    public class User{
        public int id;
        public Set<Integer> followed;
        //只保存tweet head,節省空間
        public Tweet tweet_head;

        public User(int id){
            this.id=id;
            followed = new HashSet<>();
            follow(id); // first follow itself
            tweet_head = null;
        }

        public void follow(int id){
            followed.add(id);
        }

        public void unfollow(int id){
            followed.remove(id);
        }


        // everytime user post a new tweet, add it to the head of tweet list.
        public void post(int id){
            Tweet t = new Tweet(id);
            t.next=tweet_head;
            tweet_head=t;
        }
    }




    /** Initialize your data structure here. */
    public Twitter() {
        userMap = new HashMap<Integer, User>();
    }

    /** Compose a new tweet. */
    public void postTweet(int userId, int tweetId) {
        if(!userMap.containsKey(userId)){
            User u = new User(userId);
            userMap.put(userId, u);
        }
        userMap.get(userId).post(tweetId);

    }

    //重點部分
    // Best part of this.
    // first get all tweets lists from one user including itself and all people it followed.
    // Second add all heads into a max heap. Every time we poll a tweet with
    // largest time stamp from the heap, then we add its next tweet into the heap.
    // So after adding all heads we only need to add 9 tweets at most into this
    // heap before we get the 10 most recent tweet.
    public List<Integer> getNewsFeed(int userId) {
        List<Integer> res = new LinkedList<>();

        if(!userMap.containsKey(userId))   return res;

        Set<Integer> users = userMap.get(userId).followed;
        //注意該初始化操作,指定了capacity和comparator
        PriorityQueue<Tweet> q = new PriorityQueue<Tweet>(users.size(), (a,b)->(b.time-a.time));
        for(int user: users){
            //將tweet head存入priorityQueue中
            Tweet t = userMap.get(user).tweet_head;
            // very imporant! If we add null to the head we are screwed.
            if(t!=null){
                q.add(t);
            }
        }
        int n=0;
        while(!q.isEmpty() && n<10){
            Tweet t = q.poll();
            res.add(t.id);
            n++;
            //因爲poll掉了head,所以將head.next加入進來
            if(t.next!=null)
                q.add(t.next);
        }
        return res;

    }

    /** Follower follows a followee. If the operation is invalid, it should be a no-op. */
    public void follow(int followerId, int followeeId) {
        if(!userMap.containsKey(followerId)){
            User u = new User(followerId);
            userMap.put(followerId, u);
        }
        if(!userMap.containsKey(followeeId)){
            User u = new User(followeeId);
            userMap.put(followeeId, u);
        }
        userMap.get(followerId).follow(followeeId);
    }

    /** Follower unfollows a followee. If the operation is invalid, it should be a no-op. */
    public void unfollow(int followerId, int followeeId) {
        if(!userMap.containsKey(followerId) || followerId==followeeId)
            return;
        userMap.get(followerId).unfollow(followeeId);
    }
}

2.Find Median from Data Stream

不斷變化的數組尋找中位數。

題意

參考:
1.Short simple Java
2.max,min priorityQueue

//fail 1
//timeOut
// public class MedianFinder {
//     List<Integer> list = new ArrayList<>();
//     int pointer = -1;
//     boolean even=true;

//     // Adds a number into the data structure.
//     public void addNum(int num) {
//         list.add(num);
//         Collections(list);
//         even=!even;
//         if(even==false){
//             pointer++;
//         }
//     }

//     // Returns the median of current data stream
//     public double findMedian() {
//         if(even){
//             double sum = list.get(pointer)+list.get(pointer+1);
//             return sum/(double)2;
//         }else{
//             return list.get(pointer);
//         }
//     }
// }

//success 2
//維護兩個priorityQueue
class MedianFinder {

    private Queue<Long> small = new PriorityQueue(),
                        large = new PriorityQueue();

    public void addNum(int num) {
        large.add((long) num);
        small.add(-large.poll());
        if (large.size() < small.size())
            large.add(-small.poll());
    }

    public double findMedian() {
        return large.size() > small.size()
               ? large.peek()
               : (large.peek() - small.peek()) / 2.0;
    }
}

這裏補充priorityQueue如何遍歷呢?很自然的想法是利用iterator,如下:

PriorityQueue<Integer> integers = new PriorityQueue<>(Collections.reverseOrder());//自然順序的逆序
        integers.offer(7);
        integers.offer(9);
        integers.offer(3);
        integers.offer(5);
        //test iterator
        Iterator<Integer> i = integers.iterator();
        while(i.hasNext()){
            System.out.println(i.next());
        }
        System.out.println("------");
        System.out.println("size:   "+integers.size());
        //test poll
        for (int j = 0; j < 4; j++) {
            System.out.println(integers.poll());
        }

輸出結果爲:

9
7
3
5
------
size:   4
9
7
5
3

說明在priorityQueue中利用iterator保證順序的遍歷並不靠譜!該遍歷方式沒辦法保證順序!

javaDoc中講到:

The Iterator provided in method iterator() is not guaranteed to traverse the elements of the PriorityQueue in any particular order. If you need ordered traversal, consider using Arrays.sort(pq.toArray()).

參考:
1.How to iterate over a PriorityQueue?

那麼在Stack中使用iterator有沒有這樣的錯誤呢?實驗:

Stack<Integer> integers = new Stack<>();
        integers.push(7);
        integers.push(9);
        integers.push(3);
        integers.push(5);
        //test iterator
        Iterator<Integer> i = integers.iterator();
        while(i.hasNext()){
            System.out.println(i.next());
        }
        System.out.println("------");
        System.out.println("size:   "+integers.size());
        //test pop
        for (int j = 0; j < 4; j++) {
            System.out.println(integers.pop());
        }

輸出結果:

7
9
3
5
------
size:   4
5
3
9
7

參考javaDoc-bug(沒看錯,就是個java bug),申明到:

The iterator method on java.util.Stack iterates through a Stack from the bottom
up. One would think that it should iterate as if it were popping off the top of
the Stack.

Stack的iterator是從bottom到up進行遍歷的,可以看出這種遍歷是有順序的,只是跟我們希望的從棧頂開始遍歷的方式相反而已。

其實java中關於FILO(先進後出)數據結構更好的實現方式是Deque而不是Stack(因爲兼容問題,並沒有廢除Stack,而是用Deque更好地實現,好怪,Stack居然不是Stack的最好實現,哈哈哈),參考javaDoc

The Stack class represents a last-in-first-out (LIFO) stack of objects. It extends class Vector with five operations that allow a vector to be treated as a stack. The usual push and pop operations are provided, as well as a method to peek at the top item on the stack, a method to test for whether the stack is empty, and a method to search the stack for an item and discover how far it is from the top.

When a stack is first created, it contains no items.

A more complete and consistent set of LIFO stack operations is provided by the Deque interface and its implementations, which should be used in preference to this class. For example:

   Deque<Integer> stack = new ArrayDeque<Integer>();

Deque的iterator實現就是我們想要的順序了(從棧頂up到棧底bottom的遍歷順序)!

關於heap數據結構在java中實現標準就是PriorityQueue。參考:
1.javaDoc

An unbounded priority queue based on a priority heap. 

The elements of the priority queue are ordered according to their natural ordering, or by a Comparator provided at queue construction time, depending on which constructor is used. 

A priority queue does not permit null elements.

The head of this queue is the least element with respect to the specified ordering.

The queue retrieval operations poll, remove, peek, and element access the element at the head of the queue.

The Iterator provided in method iterator() is not guaranteed to traverse the elements of the priority queue in any particular order. If you need ordered traversal, consider using Arrays.sort(pq.toArray()).

Note that this implementation is not synchronized.(不是線程安全),多線程下可以使用PriorityBlockingQueue。

時間複雜度:
Implementation note: this implementation provides O(log(n)) time for the enqueing and dequeing methods (offer, poll, remove() and add); linear time for the remove(Object) and contains(Object) methods; and constant time for the retrieval methods (peek, element, and size).

這裏乾脆直接總結一下java裏面的Collections類吧,請參考我的另一篇博客java解惑

3.Sliding Window Maximum:

Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.

For example,
Given nums = [1,3,-1,-3,5,3,6,7], and k = 3.

Window position Max
————— —–
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

Therefore, return the max sliding window as [3,3,5,5,6,7].

Note:
You may assume k is always valid, ie: 1 ≤ k ≤ input array’s size for non-empty array.

利用deque實現:

public class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length==0){
            int[] result = new int[0];
            return result;
        }
        int n = nums.length;
        int[] result = new int[n-k+1];
        Deque<Integer> deque = new ArrayDeque<>();
        for(int i=0;i<k;i++){
            deque.offer(nums[i]);
        }
        result[0] = Collections.max(deque);
        for(int i=k,j=1;i<nums.length;i++,j++){
            int num = nums[i];
            deque.pollFirst();
            deque.offerLast(num);
            result[j] = Collections.max(deque);
        }
        return result;
    }
}

4.Top K Frequent Elements

Given a non-empty array of integers, return the k most frequent elements.

For example,
Given [1,1,1,2,2,3] and k = 2, return [1,2].

Note:

You may assume k is always valid, 1 ≤ k ≤ number of unique elements.
Your algorithm's time complexity must be better than O(n log n), where n is the array's size.
public class Solution {

    Map<Integer,Ele> map = new HashMap<>();

    class Ele{
        int value;
        int count;
        public Ele(int value,int count){
            this.value=value;
            this.count=count;
        }
    }

    //降序排列
    class MyComparator implements Comparator<Ele>{

        @Override
        public int compare(Ele o1, Ele o2) {
            return o1.count==o2.count?0:(o1.count<o2.count?1:-1);
        }
    }

    public List<Integer> topKFrequent(int[] nums, int k) {
        PriorityQueue<Ele> queue = new PriorityQueue<>(new MyComparator());
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            int num = nums[i];
            if(!map.containsKey(num)){
                Ele newEle = new Ele(num,1);
                map.put(num,newEle);
            }else{
                Ele ele = map.get(num);
                ele.count++;
            }
        }
        //加入PriorityQueue中
        for (Map.Entry<Integer,Ele> each:map.entrySet()){
            queue.add(each.getValue());
        }
        while(!queue.isEmpty()&&k>0){
            list.add(queue.poll().value);
            k--;
        }
        return list;
    }
}

16.Sort

1.Valid Anagram

Given two strings s and t, write a function to determine if t is an anagram of s.

For example,
s = “anagram”, t = “nagaram”, return true.
s = “rat”, t = “car”, return false.

Note:
You may assume the string contains only lowercase alphabets.

Follow up:
What if the inputs contain unicode characters? How would you adapt your solution to such case?

//success 1
//運用排序
public class Solution {
    public boolean isAnagram(String s, String t) {
        if(s.length()!=t.length()){
            return false;
        }
        char[] sChar = s.toCharArray();
        char[] tChar = t.toCharArray();
        Arrays.sort(sChar);
        Arrays.sort(tChar);
        for (int i = 0; i < sChar.length; i++) {
            if(sChar[i]!=tChar[i]){
                return false;
            }
        }
        return true;
    }
}

2.Largest Number

Given a list of non negative integers, arrange them such that they form the largest number.

For example, given [3, 30, 34, 5, 9], the largest formed number is 9534330.

Note: The result may be very large, so you need to return a string instead of an integer.

參考:My Java Solution to share

//fail 1
//比較方法策略不好
// public class Solution {

//     class MyComparator implements Comparator<Integer>{
//         //我基於比較的思路是對的,但是將Integer化成Char[]進行比較太麻煩了,很容易出錯,棄之!
//         @Override
//         public int compare(Integer o1, Integer o2) {
//             //o1 length
//             char[] o1String = String.valueOf(o1).toCharArray();
//             int o1Length = o1String.length;
//             char[] o1Final = o1String;

//             //o2 length
//             char[] o2String = String.valueOf(o2).toCharArray();
//             int o2Length = o2String.length;
//             char[] o2Final = o2String;

//             int length = Math.min(o1Length,o2Length);
//             int i=0;
//             for (i = 0; i< length; i++) {
//                 if(o1Final[i]>o2Final[i]){
//                     return -1;
//                 }else if(o1Final[i]<o2Final[i]){
//                     return 1;
//                 }else{
//                     continue;
//                 }
//             }
//             if(o1Length>length){
//                 if(o1Final[i]>o1Final[0]){
//                     return -1;
//                 }else if(o1Final[i]<o1Final[0]){
//                     return 1;
//                 }
//             }else if(o2Length>length){
//                 if(o2Final[i]>o2Final[0]){
//                     return 1;
//                 }else if(o2Final[i]<o2Final[0]){
//                     return -1;
//                 }
//             }
//             return 0;
//         }
//     }

//     public String largestNumber(int[] nums) {
//         List<Integer> list = new ArrayList<>();
//         for (int i = 0; i < nums.length; i++) {
//             list.add(nums[i]);
//         }
//         Collections.sort(list,new MyComparator());
//         StringBuilder sb = new StringBuilder("");
//         for (Integer each:
//              list) {
//             sb.append(each);
//         }
//         return sb.toString();
//     }
// }

//success 2
public class Solution {
     public String largestNumber(int[] num) {
        if(num == null || num.length == 0)
            return "";

        // Convert int array to String array, so we can sort later on
        String[] s_num = new String[num.length];
        for(int i = 0; i < num.length; i++)
            s_num[i] = String.valueOf(num[i]);

        // Comparator to decide which string should come first in concatenation
        Comparator<String> comp = new Comparator<String>(){
            //很簡潔的比較策略
            @Override
            public int compare(String str1, String str2){
            String s1 = str1 + str2;
            String s2 = str2 + str1;
            return s2.compareTo(s1); // reverse order here, so we can do append() later
            }
            };

        Arrays.sort(s_num, comp);
                // An extreme edge case by lc, say you have only a bunch of 0 in your int array
                if(s_num[0].charAt(0) == '0')
                    return "0";

        StringBuilder sb = new StringBuilder();
        for(String s: s_num)
                sb.append(s);

        return sb.toString();

    }
}

3.Maximum Gap:

Given an unsorted array, find the maximum difference between the successive elements in its sorted form.

Try to solve it in linear time/space.

Return 0 if the array contains less than 2 elements.

You may assume all elements in the array are non-negative integers and fit in the 32-bit signed integer range.

利用bucket sort的思想,參考:[bucket sort] JAVA solution with explanation, O(N) time and space,已經寫的很詳細了。

public class Solution {
public int maximumGap(int[] num) {
    if (num == null || num.length < 2)
        return 0;
    // get the max and min value of the array
    int min = num[0];
    int max = num[0];
    for (int i:num) {
        min = Math.min(min, i);
        max = Math.max(max, i);
    }
    // the minimum possibale gap, ceiling of the integer division
    int gap = (int)Math.ceil((double)(max - min)/(num.length - 1));
    int[] bucketsMIN = new int[num.length - 1]; // store the min value in that bucket
    int[] bucketsMAX = new int[num.length - 1]; // store the max value in that bucket
    Arrays.fill(bucketsMIN, Integer.MAX_VALUE);
    Arrays.fill(bucketsMAX, Integer.MIN_VALUE);
    // put numbers into buckets
    for (int i:num) {
        if (i == min || i == max)
            continue;
        int idx = (i - min) / gap; // index of the right position in the buckets
        bucketsMIN[idx] = Math.min(i, bucketsMIN[idx]);
        bucketsMAX[idx] = Math.max(i, bucketsMAX[idx]);
    }
    // scan the buckets for the max gap
    int maxGap = Integer.MIN_VALUE;
    int previous = min;
    for (int i = 0; i < num.length - 1; i++) {
        if (bucketsMIN[i] == Integer.MAX_VALUE && bucketsMAX[i] == Integer.MIN_VALUE)
            // empty bucket
            continue;
        // min value minus the previous value is the current gap
        maxGap = Math.max(maxGap, bucketsMIN[i] - previous);
        // update previous bucket value
        previous = bucketsMAX[i];
    }
    maxGap = Math.max(maxGap, max - previous); // updata the final max value gap
    return maxGap;
}
}

4.Merge Intervals:

Given a collection of intervals, merge all overlapping intervals.

For example,
Given [1,3],[2,6],[8,10],[15,18],
return [1,6],[8,10],[15,18].

/**
 * Definition for an interval.
 * public class Interval {
 *     int start;
 *     int end;
 *     Interval() { start = 0; end = 0; }
 *     Interval(int s, int e) { start = s; end = e; }
 * }
 */


public class Solution {
    class MyComparator implements Comparator<Interval>{

        @Override
        public int compare(Interval o1, Interval o2) {
            if(o1.start<o2.start){
                return -1;
            }else if(o1.start==o2.start){
                if(o1.end<o2.end){
                    return -1;
                }else if(o1.end==o2.end){
                    return 0;
                }else{
                    return 1;
                }
            }else{
                return 1;
            }
        }
    }

    public List<Interval> merge(List<Interval> intervals) {
        if(intervals.size()==0||intervals.size()==1){
            return intervals;
        }
        intervals.sort(new MyComparator());
        Collections.reverse(intervals);
        int size = intervals.size();
        for (int i = size-2; i >=0; i--) {
            Interval cur = intervals.get(i);
            Interval pre = intervals.get(i+1);
            //overlap
            if(cur.start<=pre.end){
                cur.start = Math.min(cur.start,pre.start);
                cur.end = Math.max(cur.end,pre.end);
                intervals.remove(pre);
            }
        }
        return intervals;
    }
}

17.Bit Manipulation

關於Bit Manipulation的總結:A summary: how to use bit manipulation to solve problems easily and efficiently

1.Convert a Number to Hexadecimal:

Given an integer, write an algorithm to convert it to hexadecimal. For negative integer, two’s complement method is used.

Note:

1.All letters in hexadecimal (a-f) must be in lowercase.
2.The hexadecimal string must not contain extra leading 0s. If the number is zero, it is represented by a single zero character '0'; otherwise, the first character in the hexadecimal string will not be the zero character.
3.The given number is guaranteed to fit within the range of a 32-bit signed integer.
4.You must not use any method provided by the library which converts/formats the number to hex directly.
/*
Basic idea: each time we take a look at the last four digits of
            binary verion of the input, and maps that to a hex char
            shift the input to the right by 4 bits, do it again
            until input becomes 0.

*/

public class Solution {

    char[] map = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};

    public String toHex(int num) {
        if(num == 0) return "0";
        String result = "";
        while(num != 0){
            result = map[(num & 15)] + result; 
            num = (num >>> 4);//無符號右移
        }
        return result;
    }
}

2.Sum of Two Integers

Calculate the sum of two integers a and b, but you are not allowed to use the operator + and -.

Example:
Given a = 1 and b = 2, return 3.

關於Bit Manipulation的總結:A summary: how to use bit manipulation to solve problems easily and efficiently

我自己的failed解法:

//fail 1
//timeOut
public class Solution {
    public int getSum(int a, int b) {
        String aString =Integer.toBinaryString(a);
        String bString = Integer.toBinaryString(b);
        String result = "";
        int length = Math.max(aString.length(),bString.length());
        //align
        for (int i = 0; i < length-aString.length(); i++) {
            aString = "0"+aString;
        }
        for (int i = 0; i < length-bString.length(); i++) {
            bString = "0"+bString;
        }
        int carry=0;
        for (int i = length-1; i >=0; i++) {
            int sum1 = aString.indexOf(i);
            int sum2 = bString.indexOf(i);
            int sum = sum1+sum2+carry;
            carry = sum/2;
            result = sum%2+result;
        }
        int finalResult = Integer.parseInt(result,2);
        return finalResult;
    }
}

參考:Java simple easy understand solution with explanation

自己的success實現:

//success 2
public class Solution {
    public int getSum(int a, int b) {
        while((a&b)!=0){
            int tmp1 = ((a&b)<<1);
            int tmp2 = (a^b);
            a = tmp1;
            b = tmp2;
        }
        return a|b;
    }
}

需要再回顧Bitwise相關知識。

1.two’s complement representation(補碼)

參考:補碼-百度百科

計算機中的符號數有三種表示方法,即原碼反碼補碼。三種表示方法均有符號位和數值位兩部分,符號位都是用0表示“正”,用1表示“負”,而數值位,三種表示方法各不相同。

在計算機系統中,數值一律用補碼來表示和存儲。原因在於,使用補碼,可以將符號位和數值域統一處理;同時,加法和減法也可以統一處理。此外,補碼與原碼相互轉換,其運算過程是相同的,不需要額外的硬件電路。

補碼特性

1.一個負整數(或原碼)與其補數(或補碼)相加,和爲模。
2.對一個整數的補碼再求補碼,等於該整數自身。
3.補碼的正零與負零表示方法相同。

模的概念可以幫助理解補數和補碼。
“模”是指一個計量系統的計數範圍。如時鐘等。計算機也可以看成一個計量機器,它也有一個計量範圍,即都存在一個“模”。
例如:
時鐘的計量範圍是0~11,模=12。表示n位的計算機計量範圍是0~2^(n)-1,模=2^(n)。

“模”實質上是計量器產生“溢出”的量,它的值在計量器上表示不出來,計量器上只能表示出模的餘數
在以12模的系統中,加8和減4效果是一樣的,因此凡是減4運算,都可以用加8來代替。對“模”而言,8和4互爲補數。實際上以12模的系統中,11和1,10和2,9和3,7和5,6和6都有這個特性。共同的特點是兩者相加等於模。
對於計算機,其概念和方法完全一樣。n位計算機,設n=8, 所能表示的最大數是11111111,若再加1成爲100000000(9位),但因只有8位,最高位1自然丟失。又回了00000000,所以8位二進制系統的模爲2^8。在這樣的系統中減法問題也可以化成加法問題,只需把減數用相應的補數表示就可以了。把補數用到計算機對數的處理上,就是補碼

求整數補碼

1.正數:正整數的補碼是其二進制表示,與原碼相同。
2.負數:求負整數的補碼,將其對應正數二進制表示所有位取反(包括符號位,0變1,1變0)後加1。

補碼化爲原碼

已知一個數的補碼,求原碼的操作其實就是對該補碼再求補碼:

1.如果補碼的符號位爲“0”,表示是一個正數,其原碼就是補碼。
2.如果補碼的符號位爲“1”,表示是一個負數,那麼求給定的這個補碼的補碼就是要求的原碼。

有了補碼的知識,我們再來參考Bitwise Operators Part 1

基本操作& | ^ ~
值得一提的是取反~操作:
因爲計算機內都用補碼錶示,於是有如下公式:~(x)==(-x)-1。如下:

Integer test1 = new Integer(-10);
        System.out.println(~test1);
        Integer test2 = new Integer(+10);
        System.out.println(~test2);
        Integer test3 = new Integer(0);
        System.out.println(~test3);
        Integer test4 = new Integer(-0);
        System.out.println(~test4);

輸出爲:

9
-11
-1
-1

解釋:拿-10來說,假設爲8位,計算機內存儲的是它的補碼11110110,取反爲00001001,那麼這個取反後的補碼對應的源碼爲9(最後結果)。

高級操作<< >> >>>
左移<<:左移1相當於乘以2
右移>>:右移1相當於除以2
關於右移:40>>5=40/32=1(不爲1.25)
邏輯右移>>>:對於正數,跟右移是一樣的效果,對於複數就不是了,如下:

System.out.println(40>>1);
        System.out.println(40>>>1);
        System.out.println(-1>>1);
        System.out.println(-1>>>1);

輸出:

20
20
-1
2147483647

這是因爲對於普通右移來說,編譯器會將其符號位記下,然後在移動後添加上去,而對於邏輯右移,其符號位也是右移的一部分,拿-1舉例,假設爲8位,其在計算機中存儲爲11111111(補碼),邏輯右移後爲01111111,即爲7。可以看出改變了符號位。示例:

//int中固定的符號位爲最高位
        //下列都是補碼的無符號表示形式
        System.out.println(Integer.toBinaryString(-1));//
        System.out.println(Integer.toBinaryString((1<<31)-1));//32位int最大整數
        //print認爲括號內爲補碼,要轉化爲源碼進行輸出
        System.out.println((-1&((1<<31)-1))+1);
        System.out.println(-1|((1<<31)-1));
        System.out.println(1<<31);
        System.out.println(1<<30);

輸出:

11111111111111111111111111111111
1111111111111111111111111111111
-2147483648
-1
-2147483648
1073741824

關於java中的signed與unsigned:Signed and unsigned data types in java

Java only supports signed types (except char) because it was assumed that one type was simpler for beginners to understand than having two types for each size.

Java 8 will have operations to support unsigned types as well. These are added to the wrapper classes like Integer and Long.

如何將signed integer轉換爲unsigned long呢?參考:Best way to convert a signed integer to an unsigned long?

public static long getUnsignedInt(int x) {
    return x & 0x00000000ffffffffL;
}

3.Power of Two:

Given an integer, write a function to determine if it is a power of two. 解答:

public class Solution {
    public boolean isPowerOfTwo(int n) {
        if(n<=0){
            // n = ~n+1;
            return false;
        }
        if(n==1){
            return true;
        }
        while(n>1){
            int right = n>>1;
            if(right*2!=n){
                return false;
            }
            n=right;
        }
        return n==1;
    }
}

4.Maximum Product of Word Lengths

Given a string array words, find the maximum value of length(word[i]) * length(word[j]) where the two words do not share common letters. You may assume that each word will contain only lower case letters. If no such two words exist, return 0.

Example 1:

Given [“abcw”, “baz”, “foo”, “bar”, “xtfn”, “abcdef”]
Return 16
The two words can be “abcw”, “xtfn”.

Example 2:

Given [“a”, “aa”, “aaa”, “aaaa”]
Return 0
No such pair of words.

解答:

//success 1
//利用32位bit來存儲包含哪些字母的信息,然後&比較是否有重疊
public class Solution {
    public int maxProduct(String[] words) {
        int max=Integer.MIN_VALUE;
        int[] mask = new int[words.length];
        int[] lens = new int[words.length];
        for(int i = 0; i < words.length; ++i) lens[i] = words[i].length();
        for (int i=0; i<words.length; ++i) {
            char[] each = words[i].toCharArray();
            for (char c : each)
                mask[i] |= 1 << (c - 'a');
            for (int j=0; j<i; ++j)
                if ((mask[i] & mask[j])==0)
                    max = Math.max(max, lens[i]*lens[j]);
        }
        return max==Integer.MIN_VALUE?0:max;
    }
}

5.Counting Bits

Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1’s in their binary representation and return them as an array.

Example:
For num = 5 you should return [0,1,1,2,1,2].

Follow up:

It is very easy to come up with a solution with run time O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly in a single pass?
Space complexity should be O(n).
Can you do it like a boss? Do it without using any builtin function like __builtin_popcount in c++ or in any other language.

Hint:

You should make use of what you have produced already.

解答:

//success 1
//通過觀察0-8之間數的關係,可以看出下列關係
//An easy recurrence for this problem is 
//f[i] = f[i / 2] + i % 2.
public class Solution {
    public int[] countBits(int num) {
    int[] f = new int[num + 1];
    for (int i=1; i<=num; i++) f[i] = f[i >> 1] + (i & 1);
    return f;
}
}

6.Bitwise AND of Numbers Range

Given a range [m, n] where 0 <= m <= n <= 2147483647, return the bitwise AND of all numbers in this range, inclusive.

For example, given the range [5, 7], you should return 4.

參考:(Bit operation solution(JAVA))

The idea is very simple:

1.last bit of (odd number & even number) is 0.
2.when m != n, There is at least an odd number and an even number, so the last bit position result is 0.
3.Move m and n rigth a position.

Keep doing step 1,2,3 until m equal to n, use a factor to record the iteration time.

上述該思想的實現:

//fail 1
//timeOut
// public class Solution {
//     public int rangeBitwiseAnd(int m, int n) {
//         int result=m;
//         for (int i = m+1; i <= n; i++) {
//             result &=i;
//             if(result==0){
//                 return 0;
//             }
//         }
//         return result;
//     }
// }

//success 2
//理解:拿12(1100)和15(1111)來說,當m=n=3時終止,即當原兩數的最高几位都相同時,程序終止。
public class Solution {
    public int rangeBitwiseAnd(int m, int n) {
        if(m == 0){
            return 0;
        }
        int moveFactor = 1;
        while(m != n){
            m >>= 1;
            n >>= 1;
            moveFactor <<= 1;
        }
        return m * moveFactor;
    }
}

18.Backtracking

強烈推薦看套路A general approach to backtracking questions in Java

1.N-Queens:

題意,參考視頻N Queen Problem Using Backtracking Algorithm,以及源代碼NQueenProblem.java

自己動手實現一下咯,下面是版本一,上述問題的簡化版,即判斷是否有解,如果有解,給出一種解:

public class Solution {

    //是否存在solution,如存在,給出一種解
    public Position[] solveNQueenOneSolution(int n){
        Position[] positions = new Position[n];//初始化positions,並作爲傳入參數
        boolean hasSolution = solveNQueenOneSolutionUtil(n,0,positions);
        if(hasSolution){
            return positions;
        }else{
            return new Position[0];
        }
    }

    private boolean solveNQueenOneSolutionUtil(int n,int row,Position[] positions){
        if(n==row){
            return true;
        }
        int col;
        for (col = 0; col < n; col++) {
            boolean foundSafe = true;
            //check if this row and col is not under attack from any previous queen.
            for (int queen = 0; queen < row; queen++) {
                //不能在同一列和兩條對角線上
                if((positions[queen].col==col)||(positions[queen].row-positions[queen].col==row-col)||(positions[queen].row+positions[queen].col==row+col)){
                    foundSafe = false;
                    break;
                }
            }
            if(foundSafe){
                positions[row]=new Position(row,col);
                if(solveNQueenOneSolutionUtil(n,row+1,positions)){
                    return true;
                }
            }
        }
        return false;
    }

    public static void main(String[] args){
        Solution s = new Solution();
        Position[] positions = s.solveNQueenOneSolution(6);
        Arrays.stream(positions).forEach(position -> System.out.println(position.row + " " + position.col));
    }
}

class Position{
    int row,col;
    Position(int row,int col){
        this.row = row;
        this.col = col;
    }
}

輸出爲:

0 1
1 3
2 5
3 0
4 2
5 4

正確!

下面我們來版本二,版本二是版本一的升級,要判斷是否有解的同時還要將所有的解都找出來。版本一隻是找一個可行解(無解爲空),版本二找出所有解(無解爲空)。既然要找出所有解,那麼就不應該再return boolean。在版本一的基礎上進行修改得出版本二:

public class Solution {

    public List<List<String>> solveNQueenSolution(int n){
        List<List<String>> result = new ArrayList<>();
        Position[] positions = new Position[n];//初始化positions,並作爲傳入參數
        solveNQueenSolutionUtil(n,0,positions,result);
        return result;
    }

    private void solveNQueenSolutionUtil(int n,int row,Position[] positions,List<List<String>> result){
        if(n==row){
            //將結果添加進result
            StringBuilder sb = new StringBuilder();
            List<String> oneResult = new ArrayList<>();
            for (Position p:
                 positions) {
                for (int i = 0; i < n; i++) {
                    if(p.col==i){
                        sb.append("Q");
                    }else{
                        sb.append(".");
                    }
                }
                oneResult.add(sb.toString());
                sb = new StringBuilder();
            }
            result.add(oneResult);
            return;
        }
        int col;
        for (col = 0; col < n; col++) {
            boolean foundSafe = true;
            //check if this row and col is not under attack from any previous queen.
            for (int queen = 0; queen < row; queen++) {
                //不能在同一列和兩條對角線上
                if((positions[queen].col==col)||(positions[queen].row-positions[queen].col==row-col)||(positions[queen].row+positions[queen].col==row+col)){
                    foundSafe = false;
                    break;
                }
            }
            if(foundSafe){
                positions[row]=new Position(row,col);
                solveNQueenSolutionUtil(n,row+1,positions,result);
            }
        }
    }

    public static void main(String[] args){
        Solution s = new Solution();
        List<List<String>> result = s.solveNQueenSolution(6);
        int count=0;
        for (List<String> list:
             result) {
            System.out.println(count+"th solution:  ");
            for (String string:
                 list) {
                System.out.println(string);
            }
            count++;
        }
    }
}

class Position{
    int row,col;
    Position(int row,int col){
        this.row = row;
        this.col = col;
    }
}

運行結果爲:

0th solution:  
.Q....
...Q..
.....Q
Q.....
..Q...
....Q.
1th solution:  
..Q...
.....Q
.Q....
....Q.
Q.....
...Q..
2th solution:  
...Q..
Q.....
....Q.
.Q....
.....Q
..Q...
3th solution:  
....Q.
..Q...
Q.....
.....Q
...Q..
.Q....

正確!

回到本題上來,思想都是一樣的,稍微修改之:

public class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> result = new ArrayList<>();
        Position[] positions = new Position[n];//初始化positions,並作爲傳入參數
        solveNQueenSolutionUtil(n,0,positions,result);
        return result;
    }

    private void solveNQueenSolutionUtil(int n,int row,Position[] positions,List<List<String>> result){
        if(n==row){
            //將結果添加進result
            StringBuilder sb = new StringBuilder();
            List<String> oneResult = new ArrayList<>();
            for (Position p:
                 positions) {
                for (int i = 0; i < n; i++) {
                    if(p.col==i){
                        sb.append("Q");
                    }else{
                        sb.append(".");
                    }
                }
                oneResult.add(sb.toString());
                sb = new StringBuilder();
            }
            result.add(oneResult);
            return;
        }
        int col;
        for (col = 0; col < n; col++) {
            boolean foundSafe = true;
            //check if this row and col is not under attack from any previous queen.
            for (int queen = 0; queen < row; queen++) {
                //不能在同一列和兩條對角線上
                if((positions[queen].col==col)||(positions[queen].row-positions[queen].col==row-col)||(positions[queen].row+positions[queen].col==row+col)){
                    foundSafe = false;
                    break;
                }
            }
            if(foundSafe){
                positions[row]=new Position(row,col);
                solveNQueenSolutionUtil(n,row+1,positions,result);
            }
        }
    }
}

class Position{
    int row,col;
    Position(int row,int col){
        this.row = row;
        this.col = col;
    }
}

這裏提一句關於遞歸的變量變化的問題:
在上述程序中row==3時positions的值發生變化,但當row==3時,取所有col都不行時,將會跳至row==2。那麼這時問題來了!這時row==2取到的positions會是改變後的positions嗎?答案是肯定的!也即是called method中mutable Object對象發生的變化會影響到calling method。爲什麼在印象中String就不會呢?因爲String爲immutable Object。immutable Object不能夠通過引用來改變它的值,舉例來說:

String a = "a";
        String b=a;
        b = "b";
        System.out.println(System.identityHashCode(a));
        System.out.println(System.identityHashCode(b));
        Position p= new Position(1,2);;
        Position sp = p;
        sp.row = 3;
        System.out.println(sp.row);
        System.out.println(System.identityHashCode(p));
        System.out.println(System.identityHashCode(sp));

輸出爲(System.identityHashCode(Object)相當於打印出對象的內存地址):

21685669
2133927002
3
1836019240
1836019240

關於java中內存地址的問題,可以參考:
1.How to print the address of an object if you have redefined toString method

2.Memory address of variables in Java

Getting the memory addresses of variables is meaningless within Java, since the JVM is at liberty to implement objects and move them as it seems fit (your objects may/will move around during garbage collection etc.)

3.How can I get the memory location of a object in java?

從這裏可以看出immutable Object與mutable Object的區別。

更多關於immutable Object的資料:
1.Immutable Objects

An object is considered immutable if its state cannot change after it is constructed. Maximum reliance on immutable objects is widely accepted as a sound strategy for creating simple, reliable code.

Immutable objects are particularly useful in concurrent applications. Since they cannot change state, they cannot be corrupted by thread interference or observed in an inconsistent state.

2.A Synchronized Class Example

一個傳統的同步class的例子。

3.A Strategy for Defining Immutable Objects

改寫爲immutable形式來實現同步。

4.What is meant by immutable?

Immutable means that once the constructor for an object has completed execution that instance can’t be altered.

This is useful as it means you can pass references to the object around, without worrying that someone else is going to change its contents. Especially when dealing with concurrency, there are no locking issues with objects that never change.

2.String Permutation Algorithm

輸出該組合的所有排列(可以處理含重複char的char[])

public class Solution {
    public List<String> permute(char input[]) {
        Map<Character,Integer> map = new TreeMap<>();
        for (char eachInput:
             input) {
            if(!map.containsKey(eachInput)){
                map.put(eachInput,1);
            }else{
                map.put(eachInput,map.get(eachInput)+1);
            }
        }
        char[] str = new char[map.size()];
        int[] count = new int[map.size()];
        int index=0;
        for (Map.Entry<Character,Integer> eachMap:
             map.entrySet()) {
            str[index] = eachMap.getKey();
            count[index] = eachMap.getValue();
            index++;
        }
        List<String> resultList = new ArrayList<>();
        char[] result = new char[input.length];
        permuteUtil(str,count,result,resultList,0);
        return resultList;
    }

    void permuteUtil(char[] str,int[] count,char[] result,List resultList,int level){
        if(level==result.length){
            addResult(result,resultList);
        }
        for (int i = 0; i < str.length; i++) {
            if(count[i]==0){
                continue;
            }
            count[i]--;
            result[level] = str[i];
            permuteUtil(str,count,result,resultList,level+1);
            count[i]++;
        }
    }
    void addResult(char[] result,List resultList){
        StringBuilder sb = new StringBuilder();
        for (char each:
             result) {
            sb.append(each);
        }
        resultList.add(sb.toString());
    }

    public static void main(String[] args){
        Solution s = new Solution();
        char[] input = {'a','a','b','c','c','c'};
        List<String> result;
        result = s.permute(input);
        for (String each:
             result) {
            System.out.println(each);
        }
        System.out.println(result.size());
    }
}

leetcode中有一道相似的題:

3.ermutation Sequence

The set [1,2,3,…,n] contains a total of n! unique permutations.

By listing and labeling all of the permutations in order,
We get the following sequence (ie, for n = 3):

"123"
"132"
"213"
"231"
"312"
"321"

Given n and k, return the kth permutation sequence.

稍微改寫上述代碼即可:

//fail 1
//timeOut
public class Solution {

    public String getPermutation(int n, int k) {
        char[] input=new char[n];
        for (int i = 0; i < n; i++) {
            char ss = (char)(i+1+'0');
            input[i] = ss;
        }
        List<String> result;
        result = permute(input,k);
        return result.get(0);
    }

    public List<String> permute(char input[],int kk) {
        Map<Character,Integer> map = new TreeMap<>();
        for (char eachInput:
             input) {
            if(!map.containsKey(eachInput)){
                map.put(eachInput,1);
            }else{
                map.put(eachInput,map.get(eachInput)+1);
            }
        }
        char[] str = new char[map.size()];
        int[] count = new int[map.size()];
        int index=0;
        for (Map.Entry<Character,Integer> eachMap:
             map.entrySet()) {
            str[index] = eachMap.getKey();
            count[index] = eachMap.getValue();
            index++;
        }
        List<String> resultList = new ArrayList<>();
        char[] result = new char[input.length];
        //不用基本類型
        Counter k = new Counter(kk);
        permuteUtil(str,count,result,resultList,0,k);
        return resultList;
    }

    void permuteUtil(char[] str,int[] count,char[] result,List resultList,int level,Counter k){
        if(level==result.length){
            k.count--;
            if(k.count==0){
                addResult(result,resultList);
                return;
            }
        }
        for (int i = 0; i < str.length; i++) {
            if(count[i]==0){
                continue;
            }
            count[i]--;
            result[level] = str[i];
            permuteUtil(str,count,result,resultList,level+1,k);
            count[i]++;
        }
    }
    void addResult(char[] result,List resultList){
        StringBuilder sb = new StringBuilder();
        for (char each:
             result) {
            sb.append(each);
        }
        resultList.add(sb.toString());
    }
}
class Counter{
    int count=0;
    public Counter(int count){
        this.count=count;
    }
}

上述存儲爲什麼不用Integer來表示呢?(基本類型int肯定不行)。爲什麼呢?
參考:
1.Why are Integers immutable in Java?
2.Are Java primitives immutable?

運行結果正確,但是運行時間太長。改善之:
參考“Explain-like-I’m-five” Java Solution in O(n),代碼:

public class Solution {
    StringBuilder sb = new StringBuilder();
    public String getPermutation(int n, int k) {
        //因爲需要從中間除去num,爲了性能,不用ArrayList
        k--;
        List<Integer> nums = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            nums.add(i+1);
        }
        n--;
        int factorial = 1;
        for (int i = 0; i < n; i++) {
            factorial*=(i+1);
        }
        DFS(factorial,n,k,nums);
        return sb.toString();
    }
    void DFS(int factorial,int n,int k,List<Integer> nums){
        if(n==0){
            sb.append(nums.get(0));
            return;
        }else{
            int index = k/factorial;
            int num = nums.remove(index);
            sb.append(num);
            k -= index*factorial;
            factorial /= n;
            n--;
            DFS(factorial,n,k,nums);
        }
    }
}

仔細理解該算法的思想,其實就是從最高level來不斷地減數來輸出正確的順序。

4.Regular Expression Matching

Implement regular expression matching with support for ‘.’ and ‘*’.

‘.’ Matches any single character.
‘*’ Matches zero or more of the preceding element.

The matching should cover the entire input string (not partial).

The function prototype should be:
bool isMatch(const char *s, const char *p)

Some examples:
isMatch(“aa”,”a”) → false
isMatch(“aa”,”aa”) → true
isMatch(“aaa”,”aa”) → false
isMatch(“aa”, “a*”) → true
isMatch(“aa”, “.*”) → true
isMatch(“ab”, “.*”) → true
isMatch(“aab”, “c*a*b”) → true

可以用DP的實現來實現,參考:
1.Tushar Roy大神的視頻講解:Regular Expression Dynamic Programming
2.Easy DP Java Solution with detailed Explanation
實現:

public class Solution {
    //using DP
    public boolean isMatch(String s, String p) {
        boolean[][] dp = new boolean[s.length()+1][p.length()+1];
        dp[0][0] = true;
        //dealing with the special cases:a*,a*b*,a*b*c* and so on
        //the 0th row
        for (int i = 1; i < dp[0].length;i++) {
            if(p.charAt(i-1)=='*'){
                dp[0][i] = dp[0][i-2];
            }
        }
        //key point
        for (int i = 1; i < dp.length; i++) {
            for (int j = 1; j < dp[0].length; j++) {
                if((s.charAt(i-1)==p.charAt(j-1))||p.charAt(j-1)=='.'){
                    dp[i][j] = dp[i-1][j-1];
                }else if(p.charAt(j-1)=='*'){
                    //0 occurence
                    dp[i][j] = dp[i][j-2];
                    if((s.charAt(i-1)==p.charAt(j-2))||p.charAt(j-2)=='.'){
                        dp[i][j] = dp[i][j]||dp[i-1][j];
                    }
                }else{
                    dp[i][j] = false;
                }
            }
        }
        return dp[dp.length-1][dp[0].length-1];
    }
}

5.Combination Sum

Given a set of candidate numbers (C) (without duplicates) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

The same repeated number may be chosen from C unlimited number of times.

Note:
All numbers (including target) will be positive integers.
The solution set must not contain duplicate combinations.
For example, given candidate set [2, 3, 6, 7] and target 7,
A solution set is:
[
[7],
[2, 2, 3]
]

//fail 1
//timeOut
//自己分析耗時主要是以下幾點:
//1.爲了在最終result中不添加重複元素,進行了耗時的判斷
//2.爲了記錄棧信息,用了String,因爲如果用其他的immutable Object,called method操作會影響calling method,
//這導致add操作中對String的處理增加了耗時
// public class Solution {
//     public List<List<Integer>> combinationSum(int[] candidates, int target) {
//         List<List<Integer>> result = new ArrayList<>();
//         String string="";
//         Arrays.sort(candidates);
//         combinationSumUtil(candidates,target,result,string);
//         return result;
//     }
//     void combinationSumUtil(int[] candidates,int target,List<List<Integer>> result,String string){
//         if(target==0){
//             string = string.substring(1);
//             String[] oneResultString = string.split("\\.");
//             Integer[] oneResultInteger = new Integer[oneResultString.length];
//             for (int i = 0; i < oneResultString.length; i++) {
//                 oneResultInteger[i] = Integer.valueOf(oneResultString[i]);
//             }
//             Arrays.sort(oneResultInteger);
//             List<Integer> oneResultList = Arrays.asList(oneResultInteger);
//             for (List<Integer> each:
//                  result) {
//                 if(each.equals(oneResultList)){
//                     return;
//                 }
//             }
//             result.add(oneResultList);
//             return;
//         }
//         for (int i = 0; i < candidates.length; i++) {
//             if(candidates[i]>target){
//                 break;
//             }
//             combinationSumUtil(candidates,target-candidates[i],result,string+"."+candidates[i]);
//         }
//     }
// }

//success 2
//對比自己的實現,然後找優點
public class Solution{
    public List<List<Integer>> combinationSum(int[] nums, int target) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, target, 0);
    return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, int remain, int start){
    if(remain < 0) return;
    //這裏新new了一個list,所以並不影響棧信息!機智!
    else if(remain == 0) list.add(new ArrayList<>(tempList));
    else{ 
        for(int i = start; i < nums.length; i++){
            tempList.add(nums[i]);
            backtrack(list, tempList, nums, remain - nums[i], i); // not i + 1 because we can reuse same elements
            //重要!移出新增信息!保證了tempList的正確性!backtracking的套路中一般都有這一步!
            tempList.remove(tempList.size() - 1);
        }
    }
}
}

仔細分析關於backtracking的這幾道題,可以總結出backtracking的套路!

強烈推薦看套路A general approach to backtracking questions in Java

6.Subsets

Given a set of distinct integers, nums, return all possible subsets.

Note: The solution set must not contain duplicate subsets.

For example,
If nums = [1,2,3], a solution is:

[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]

我的錯誤解法,不僅錯,還很不簡潔:

//fail 1
//處理重複情況很不簡潔
public class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
//        List<Integer> numsList1 = Arrays.asList(nums);
//        List<Integer> numsList = new LinkedList<Integer>(numsList1);
        List<Integer> numsList = new LinkedList<>();
        for (int i = 0; i < nums.length ;i++) {
            numsList.add(nums[i]);
        }
        subsetsUtil(numsList,result,new ArrayList<>());
        List<Integer> nullResult = new ArrayList<>();
        result.add(nullResult);
        // result.add(new ArrayList<>(numsList));
        return result;
    }
    void subsetsUtil(List<Integer> nums,List<List<Integer>> result,List<Integer> tmpList){
        for (int i = 0; i < nums.size(); i++) {
            //deal with duplicate cases
            if(tmpList.size()!=0){
                if(nums.get(i)<tmpList.get(tmpList.size()-1)){
                    return;
                }
            }
            //choose one
            int num = nums.remove(i);
            tmpList.add(num);
            result.add(new ArrayList<>(tmpList));
            subsetsUtil(nums,result,tmpList);
            tmpList.remove(tmpList.size()-1);
            nums.add(i,num);
        }
    }
}

這裏補充Arrays.asList()方法的注意事項

Not every List implementation supports the add() method.

One common example is the List returned by Arrays.asList(): it is documented not to support any structural modification (i.e. removing or adding elements) :Returns a fixed-size list backed by the specified array.
參考:
1.Why I get UnsupportedOperationException when trying to remove from the List?
2.Java List.add() UnsupportedOperationException

正確的簡潔做法:

//success 2
//運用sort和start兩個點就避免了出現重複情況
public class Solution{
    public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, 0);
    return list;
}

private void backtrack(List<List<Integer>> list , List<Integer> tempList, int [] nums, int start){
    list.add(new ArrayList<>(tempList));
    for(int i = start; i < nums.length; i++){
        tempList.add(nums[i]);
        backtrack(list, tempList, nums, i + 1);
        tempList.remove(tempList.size() - 1);
    }
}
}

7.Subsets II

跟Subsets題類似,加入了duplicate的情況

Given a collection of integers that might contain duplicates, nums, return all possible subsets.

Note: The solution set must not contain duplicate subsets.

For example,
If nums = [1,2,2], a solution is:

[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

跟上述解法類似,加入了處理duplicate情況的步驟:

public class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, 0);
    return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, int start){
    list.add(new ArrayList<>(tempList));
    for(int i = start; i < nums.length; i++){
        if(i > start && nums[i] == nums[i-1]) continue; // skip duplicates
        tempList.add(nums[i]);
        backtrack(list, tempList, nums, i + 1);
        tempList.remove(tempList.size() - 1);
    }
} 
}

8.Permutations II

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

For example,
[1,1,2] have the following unique permutations:

跟本節中的2.String Permutation Algorithm 簡直一模一樣!稍微改寫之:

public class Solution {
    public List<List<Integer>> permuteUnique(int input[]) {
        Map<Integer, Integer> map = new TreeMap<>();
        for (int eachInput :
                input) {
            if (!map.containsKey(eachInput)) {
                map.put(eachInput, 1);
            } else {
                map.put(eachInput, map.get(eachInput) + 1);
            }
        }
        int[] str = new int[map.size()];
        int[] count = new int[map.size()];
        int index = 0;
        for (Map.Entry<Integer, Integer> eachMap :
                map.entrySet()) {
            str[index] = eachMap.getKey();
            count[index] = eachMap.getValue();
            index++;
        }
        List<List<Integer>> resultList = new ArrayList<>();
        int[] result = new int[input.length];
        permuteUtil(str, count, result, resultList, 0);
        return resultList;
    }

    void permuteUtil(int[] str, int[] count, int[] result, List<List<Integer>> resultList, int level) {
        if (level == result.length) {
            addResult(result, resultList);
        }
        for (int i = 0; i < str.length; i++) {
            if (count[i] == 0) {
                continue;
            }
            count[i]--;
            result[level] = str[i];
            permuteUtil(str, count, result, resultList, level + 1);
            count[i]++;
        }
    }

    void addResult(int[] result, List resultList) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < result.length; i++) {
            list.add(result[i]);
        }
        resultList.add(list);
    }
}

9.Combination Sum II

Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

Each number in C may only be used once in the combination.

Note:
All numbers (including target) will be positive integers.
The solution set must not contain duplicate combinations.
For example, given candidate set [10, 1, 2, 7, 6, 1, 5] and target 8,
A solution set is:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]

參考本節中的5.Combination Sum7.Subsets II的代碼,改寫之:

public class Solution{
    public List<List<Integer>> combinationSum2(int[] nums, int target) {
        List<List<Integer>> list = new ArrayList<>();
        Arrays.sort(nums);
        backtrack(list, new ArrayList<>(), nums, target, 0);
        return list;
    }

    private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, int remain, int start){
        if(remain < 0) return;
            //這裏新new了一個list,所以並不影響棧信息!機智!
        else if(remain == 0) list.add(new ArrayList<>(tempList));
        else{
            for(int i = start; i < nums.length; i++){
                //改動1:處理重複
                if(i>start&&nums[i]==nums[i-1]){
                    continue;
                }
                tempList.add(nums[i]);
                backtrack(list, tempList, nums, remain - nums[i], i+1);//改動2:將i變爲i+1
                //重要!移出新增信息!保證了tempList的正確性!backtracking的套路中一般都有這一步!
                tempList.remove(tempList.size() - 1);
            }
        }
    }
}

10.Wildcard Matching

Implement wildcard pattern matching with support for ‘?’ and ‘*’.

‘?’ Matches any single character.
‘*’ Matches any sequence of characters (including the empty sequence).

The matching should cover the entire input string (not partial).

The function prototype should be:
bool isMatch(const char *s, const char *p)

Some examples:
isMatch(“aa”,”a”) → false
isMatch(“aa”,”aa”) → true
isMatch(“aaa”,”aa”) → false
isMatch(“aa”, “*”) → true
isMatch(“aa”, “a*”) → true
isMatch(“ab”, “?*”) → true
isMatch(“aab”, “c*a*b”) → false

注意本題跟4.Regular Expression Matching很相似!
不同點在於:

第4題

‘.’ Matches any single character.
‘*’ Matches zero or more of the preceding element.

本題

‘?’ Matches any single character.
‘*’ Matches any sequence of characters (including the empty sequence).

兩題的不同在於*的定義不同,參考Wildcard Matching Dynamic Programming,通過對比,改寫之:

public class Solution {
    //using DP
    public boolean isMatch(String s, String p) {
        boolean[][] dp = new boolean[s.length()+1][p.length()+1];
        dp[0][0] = true;
        //dealing with the special cases:*,**,*** and so on
        //the 0th row
        for (int i = 1; i < dp[0].length;i++) {
            if(p.charAt(i-1)=='*'){
                dp[0][i] = dp[0][i-1];
            }
        }
        //key point
        for (int i = 1; i < dp.length; i++) {
            for (int j = 1; j < dp[0].length; j++) {
                if((s.charAt(i-1)==p.charAt(j-1))||p.charAt(j-1)=='?'){
                    dp[i][j] = dp[i-1][j-1];
                }else if(p.charAt(j-1)=='*'){
                    dp[i][j] = dp[i][j-1]||dp[i-1][j];
                }else{
                    dp[i][j] = false;
                }
            }
        }
        return dp[dp.length-1][dp[0].length-1];
    }
}
發佈了105 篇原創文章 · 獲贊 19 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章