LeetCode:Heap,Sort,Bit Manipulation,Backtracking

这篇博客详细介绍了LeetCode中涉及的堆、排序、位操作和回溯算法的题目,包括设计Twitter、找到数据流中的中位数、滑动窗口最大值、最频繁的元素等问题。博客通过实例解析了相关算法,并讨论了Java中数据结构的实现,如PriorityQueue、Deque和Stack。同时,文章还探讨了位操作如补码的原理和应用,以及回溯算法在解决N皇后问题等经典问题上的应用。

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];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值