Leetcode之分治与回溯

目录

1.valid-palindrome

2.restore-ip-addresses

3.sqrtx

4.powx-n

5.permutations

6.permutations-ii

7.next-permutation

8.permutation-sequence


1.valid-palindrome

题目:判断题目给出的字符串是不是回文,仅考虑字符串中的字母字符和数字字符,并且忽略大小写。例如:"A man, a plan, a canal: Panama"是回文,"race a car"不是回文。注意:你有没有考虑过字符串可能为空?这是面试时应该提出的一个好问题。针对这个问题,我们定义空字符串是回文

分析:判断回文字符串只要判断第一个字符和最后一个字符是否相等,再递归判断中间的字符串是否是回文即可。因为题目中要求只考虑字母和数字,因此需忽略一些无效字符。

   public boolean isPalindrome(String s) {
        if(s.length() == 0)
            return true;
        int low = 0,high = s.length() - 1;
        while(low < high){
            //忽略除数字和字母以外的字符
            while(low < high && ! isvalid(s.charAt(low)))
                low++;
            while(low < high && ! isvalid(s.charAt(high)))
                high--;
            if(low < high && Character.toLowerCase(s.charAt(low)) != Character.toLowerCase(s.charAt(high)))
                return false;
            low++;high--;
        }
        return true;
    }

    private boolean isvalid(char c) {
        if(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9')
            return true;
        return false;
    }

2.restore-ip-addresses

题目:现在有一个只包含数字的字符串,将该字符串重新存储成IP地址的形式,返回所有可能的情况。例如:给出的字符串为"25525511135",返回["255.255.11.135", "255.255.111.35"]. (顺序没有关系)

分析:分治法。IP地址被分为四个部分,先只考虑第一部分,第一部分可能是一个数字、两个数字或者三个数字,再递归求解剩余的字符串的形式。当遍历到字符串的最后一个数字且list中有四个数字就得到了一个合法序列。

   public ArrayList<String> restoreIpAddresses(String s) {
        ArrayList<String> list = new ArrayList<>();
        if(s.length() == 0)
            return list;
        ArrayList<Integer> cur = new ArrayList<>();
        util(s,list,cur,0);
        return list;
    }

    private void util(String s, ArrayList<String> list, ArrayList<Integer> cur, int index) {
        if(cur.size() == 4 && index == s.length()){
            list.add(cur.get(0) + "." + cur.get(1) + "." + cur.get(2) + "."+ cur.get(3) );
            return;
        }
        if(cur.size() == 4 || index == s.length())
            return;
        int num = 0;
        //确定第一个数字,可能是一位、两位、三位
        for(int i = index;i < index + 3 && i < s.length();i++){
            num = num * 10 + s.charAt(i) - '0';
            if(num > 255) //剪枝
                return;
            cur.add(num);
            util(s,list,cur,i+1);
            cur.remove(cur.size() - 1);
            if(num == 0 || num > 25)//剪枝,数字的前缀不能为0
                break;
        }
    }

3.sqrtx

题目:实现函数 int sqrt(int x). 计算并返回x的平方根

分析:采用二分查找的思想,注意:不用mid*mid进行判断,可能会溢出!

   public int sqrt(int x) {
        if(x < 2)
            return x;
       int low = 1,high = x / 2;
       int mid,last_mid = 0;
       while(low <= high){
           mid = (low + high) / 2;
           if(x / mid > mid){//x > mid * mid 会溢出
               low = mid + 1;
               last_mid = mid;
           }
           else if(x / mid < mid)
               high = mid -1;
           else
               return mid;
       }
       return last_mid;
    }

4.powx-n

题目:请实现函数 pow(x, n).即,求x的n次方。

分析:见剑指offer面试题16 https://blog.csdn.net/Nibaby9/article/details/104126811

5.permutations

题目:给出一组数字,返回该组数字的所有排列。例如:[1,2,3]的所有排列如下,[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1].

分析:分治法,先只固定第一个位置的数字,再递归求解剩下数字的排列。牛客网中默认输出需按照字典序输出,所以固定第一个元素时应采用插入法进行插入。

    public ArrayList<ArrayList<Integer>> permute(int[] num) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        if(num.length == 0)
            return result;
        permute(num,result,0);
        return result;
    }

    private void permute(int[] num, ArrayList<ArrayList<Integer>> result, int index) {
        if(index == num.length - 1){
            ArrayList<Integer> list = new ArrayList<>();
            for(int i : num)
                list.add(i);
            result.add(list);
        }
        for(int i = index;i < num.length;i++){ //依次将第i个元素放到index位置上
            swap1(num,index,i);
            permute(num,result,index + 1);
            swap2(num,index,i);
        }
    }
    //将第i个元素前移到第index位置上
    private void swap1(int[] num, int index, int i) {
        int temp = num[i];
        for(int j = i;j > index;j--)
            num[j] = num[j-1];
        num[index] = temp;
    }
    //将第index个元素后移还原到第i个位置上
    private void swap2(int[] num, int index, int i) {
        int temp = num[index];
        for(int j = index;j < i;j++)
            num[j] = num[j+1];
        num[i] = temp;
    }

6.permutations-ii

题目:给出一组可能包含重复项的数字,返回该组数字的所有排列。例如:[1,1,2]的排列如下,[1,1,2],[1,2,1], [2,1,1].

分析:类似题见剑指offer面试题38 https://blog.csdn.net/Nibaby9/article/details/103822794

如果直接在上题的基础上进行去重,会时间复杂度太高而通过不了。这里采用另一种方式,将每个元素都作为第一个元素进行添加,对于已添加的元素进行已访问标记,再递归求解其它元素的全排列。由于数组中可能包含重复项,所以我们需对数组进行排序,当当前元素和前一个元素相同且前一个元素未被访问时进行剪枝。

   public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        if(num.length == 0)
            return result;
        ArrayList<Integer> list = new ArrayList<>();
        Arrays.sort(num);
        boolean[] visit = new boolean[num.length];
        permute(num,result,list,visit);
        return result;
    }

    private void permute(int[] num, ArrayList<ArrayList<Integer>> result,ArrayList<Integer> list,boolean[] visit) {
        if(list.size() == num.length){//递归出口
            result.add(new ArrayList<>(list));
            return;
        }
        for(int i = 0;i < num.length;i++){//按顺序依次将每个元素加入到list中
            if(visit[i])
                continue;
            if(i > 0 && num[i] == num[i-1] && !visit[i-1])
                continue;
            list.add(num[i]);visit[i] = true;
            permute(num,result,list,visit);
            list.remove(list.size() - 1);visit[i] = false;
        }
    }

7.next-permutation

题目:实现函数next permutation(下一个排列)。将排列中的数字重新排列成字典序中的下一个更大的排列。如果不存在这样的排列,则将其排列为字典序最小的排列(升序排列)。需要使用原地算法来解决这个问题,不能申请额外的内存空间。下面有机组样例,左边是输入的数据,右边是输出的答案1,2,3→1,3,2;3,2,1→1,2,3;1,1,5→1,5,1。

分析:首先从后往前找到第一个满足num[i] < num[i+1]的i,这样num[i]与后面的数交换才可能是下一个更大的序列;第i个位置需存放比num[i]更大的最小的数,将这两个数交换后,不难发现num[i]之后的数是从大到小排列的,将其翻转后就是我们要求的下一个更大的排列。比如说1 2 3 7 6 5 1,3就是我们首先要找的num[i],而5就是比3更大的最小的数,交换后就是1 2 5 7 6 3 1,将5后面的数字翻转后为1 2 5 1 3 6 7就是我们要求的下一个更大的排列。

   public void nextPermutation(int[] num) {
        if(num.length < 2)
            return;
        int index = num.length - 2;
        //从后往前找到第一个满足num[i] < num[i+1]的i,这样num[i]与后面的数交换才可能是之后的序列
        while(index >= 0 && num[index] >= num[index+1])
            index--;
        if(index >= 0){
            //找到比num[i]更大的最小的数
            int j = index + 1;
            while(j < num.length && num[j] > num[index])
                j++;
            swap(num,index,--j);
        }
        //交换后实际上num[i]后面的数会从大到小排列,将其翻转就可从小到大
        int low = index + 1,high = num.length - 1;
        while(low < high)
            swap(num,low++,high--);
    }

    private void swap(int[] num, int index, int j) {
        int temp = num[index];
        num[index] = num[j];
        num[j] = temp;
    }

8.permutation-sequence

题目:集合[1,2,3,…,n]一共有n!种不同的排列,按字典序列出所有的排列并且给这些排列标上序号,我们就会得到以下的序列(以n=3为例)

  1. "123"
  2. "132"
  3. "213"
  4. "231"
  5. "312"
  6. "321"

现在给出n和k,请返回第k个排列。注意:n在1到9之间

分析:可以利用求全排列的方法进行暴力求解,但时间空间复杂度都比较大。这里采用另一种方式,每次只找开头的元素,每个元素开头的排列有n! / n种,也就是我们将n! 种元素转化为一个n行n! / n列的矩阵,第k个元素所在的行数记为row,而第row个未被访问的数字,就是我们要找的开头的元素。第k个元素所在的列记为col,又把问题转化成了再该行中找第col个序列,递归求解即可。

    public String getPermutation(int n, int k) {
        if(n == 0)
            return "";
        int sum = 1;
        boolean[] visit = new boolean[n];
        for(int i = 2;i <= n;i++)
            sum *= i;
        return util(n,k,sum,visit);
    }

    private String util(int n, int k, int sum, boolean[] visit) {
        if(n == 0)//递归出口
            return "";
        sum = sum / n;//相当于矩阵的列数,即相同开头数字有多少种
        int row = (k - 1) / sum + 1,col = (k - 1) % sum + 1;
        int i = 0;
        while(row > 0){//第row个未被访问的数字就是要找的第一个数字
            if(!visit[i])
                row--;
            i++;
        }
        visit[i-1] = true;
        return String.valueOf(i) + util(n-1,col,sum,visit);
    }

 

猜你喜欢

转载自blog.csdn.net/Nibaby9/article/details/104523512