[summary of interview algorithm questions 01] backtracking method

Posted by j8h9x on Sun, 20 Feb 2022 15:54:08 +0100

Series Description:

[summary of interview algorithm questions] the series mainly summarizes common algorithm questions with each algorithm as a unit. The main questions are derived from the two topics of leetcode's sword finger offer and hot100. The link is as follows:
Sword finger offer and hot100

Here is a brief explanation. There may be multiple solutions to a problem, or similar problems have completely different solutions. The examples I summarize in each article do not necessarily use the algorithm written in the title, but may expand more relevant solutions and similar problems.

Anyway, the ultimate goal is to solve the problem of muddling along and lack of training. Unfamiliar code is written over and over again and understood over and over again until you can write it.

A personal suggestion to write an algorithm during the interview:

  1. Efficient operation > successful operation > failed operation: after thinking about inefficient ideas, think about whether there are efficient ideas for 3 minutes. If not, write them first with inefficient ideas to ensure successful operation first
  2. The naming of variables does not correspond to English. It is even more embarrassing to use a letter directly and hesitate to write a wrong word for a long time
  3. Write more notes, not only for the interviewer, but also for yourself
  4. Consider whether to use global variables for the values to be passed back. For others, just pass them through parameters
  5. Be confident, confident and confident before running successfully. Be confident that "it's not a trivial matter to solve this problem"
  6. After successful operation, be careful, be careful and be careful again. Be correct, robust and think about optimizing time and space

Backtracking method:

My understanding is: the depth first traversal of the tree (recursive tree)
1. The core should pay attention to the path (the choice already made), the selection list (the choice that can be made at present), and the end condition (the condition that can no longer be selected)
2 structurally, it is a loop in which the depth of the inner set is traversed first, and the state changes before and after it
 

Example 1: Full arrangement

Solution 1:

Because to know which elements have not been used, a better way is to use the num array given by itself to exchange positions.

class Solution {
    List<List<Integer>> result;
    public List<List<Integer>> permute(int[] nums) {
        result=new ArrayList<>();
        dfs(nums,0);
        return result;
    }
    public void dfs(int[] nums,int index){
        if(index==nums.length){
            List<Integer> temp=new ArrayList<>();
            for(int i=0;i<nums.length;++i){
                temp.add(nums[i]);
            }
            result.add(temp);
            return;
        }
        for(int i=index;i<nums.length;++i){		//loop
            swap(nums,index,i);		//Change state
            dfs(nums,index+1);		//Depth first traversal
            swap(nums,index,i);		//Change back to status
        }
    }
    public void swap(int[] nums,int index1,int index2){
        int temp=nums[index1];
        nums[index1]=nums[index2];
        nums[index2]=temp;
    }
}

Solution 2:

According to the characteristic that the numbers are not repeated, you can directly cycle all the elements of num to judge whether they have been used.
1. First of all, note that when adding temp to result, you need to create a new one
2 remove can delete the subscript of int type or Integer object type. For example, you can remove(index) or remove(Integer.valueOf(item))

class Solution {
    List<List<Integer>> result;
    int[] nums;
    public List<List<Integer>> permute(int[] nums) {
        result=new ArrayList<>();
        this.nums=nums;
        List<Integer> temp=new ArrayList<>();
        dfs(0,temp);
        return result;
    }
    public void dfs(int index,List<Integer> temp){
        if(index==nums.length){
            result.add(new ArrayList<>(temp));
            return;
        }
        for(int item:nums){
            if(!temp.contains(item)){
                temp.add(item);
                dfs(index+1,temp);
                temp.remove(index);
            }
        }
    }
}

 

Example 2: Full arrangement II

Solution 1:

Add a set on the basis of solution 1 of example 1 to ensure that there is no repetition in the interval of [index, num.length)

class Solution {
    List<List<Integer>> result;
    public List<List<Integer>> permuteUnique(int[] nums) {
        result=new ArrayList<>();
        dfs(nums,0);
        return result;
    }
    public void dfs(int[] nums,int index){
        if(index==nums.length){
            List<Integer> temp=new ArrayList<>();
            for(int i=0;i<nums.length;++i){
                temp.add(nums[i]);
            }
            result.add(temp);
            return;
        }
        HashSet<Integer> set=new HashSet<>();
        for(int i=index;i<nums.length;++i){		//loop
            if(set.contains(nums[i]))continue;
            set.add(nums[i]);
            swap(nums,index,i);		//Change state
            dfs(nums,index+1);		//Depth first traversal
            swap(nums,index,i);		//Change back to status
        }
    }

    public void swap(int[] nums,int index1,int index2){
        int temp=nums[index1];
        nums[index1]=nums[index2];
        nums[index2]=temp;
    }
}

Solution 2:

Based on example 1 and solution 2
1 here to save whether each number is used, and then prune according to the situation.
2 in order to facilitate pruning, the array must be sorted first.
3 for detailed description of pruning, see: Solution to the problem of Likou boss

class Solution {
    List<List<Integer>> result;
    boolean[] visited;
    int[] nums;
    public List<List<Integer>> permuteUnique(int[] nums) {
        result=new ArrayList<>();
        visited=new boolean[nums.length];
        Arrays.sort(nums);
        this.nums=nums;
        List<Integer> temp=new ArrayList<>();
        dfs(0,temp);
        return result;
    }
    public void dfs(int index,List<Integer> temp){
        if(index==nums.length){
            result.add(new ArrayList<>(temp));
        }
        for(int i=0;i<nums.length;++i){
            if(visited[i]){
                continue;
            }
            if(i>0&&nums[i]==nums[i-1]&&visited[i-1]==false){
                continue;
            }
            temp.add(nums[i]);
            visited[i]=true;
            dfs(index+1,temp);
            temp.remove(index);
            visited[i]=false;
            
        }
    }

}

 

Example 3: Path in matrix

class Solution {
    public boolean exist(char[][] board, String word) {
        char[] words=word.toCharArray();
        if(board.length==0){
            return false;
        }
        for(int i=0;i<board.length;++i){
            for(int j=0;j<board[0].length;++j){
                if(dfs(board,i,j,words,0)){
                    return true;
                }
            }
        }
        return false;
    }
    public boolean dfs(char[][] board,int row,int col,char[] word,int index){
        if(row<0||row>=board.length||col<0||col>=board[0].length||board[row][col]!=word[index]){
            return false;
        }
        if(index==word.length-1){
            return true;
        }
        board[row][col]='\0';
        boolean result=dfs(board,row-1,col,word,index+1)||dfs(board,row+1,col,word,index+1)||dfs(board,row,col-1,word,index+1)||dfs(board,row,col+1,word,index+1);
        board[row][col]=word[index];
        return result;
    }

}

 

Example 4: Sword finger Offer 13 Range of motion of robot

Solution 1: backtracking method

class Solution {
    boolean[][] board;
    public int movingCount(int m, int n, int k) {
        board=new boolean[m][n];
        dfs(0,0,m,n,k);
        int result=0;
        for(int i=0;i<m;++i){
            for(int j=0;j<n;++j){
                if(board[i][j]){
                    ++result;
                }
            }
        }
        return result;
    }

    public void dfs(int row,int col,int m, int n, int k){
        if(row<0||row>=m||col<0||col>=n||countNum(row)+countNum(col)>k||board[row][col]){
            return;
        }
        board[row][col]=true;
        dfs(row-1,col,m,n,k);
        dfs(row+1,col,m,n,k);
        dfs(row,col-1,m,n,k);
        dfs(row,col+1,m,n,k);

    }

    public int countNum(int num){
        int sum=0;
        while(num!=0){
            int temp=num%10;
            sum+=temp;
            num/=10;
        }
        return sum;
    }
}

Solution 2: recursive method

In fact, this problem can only go down and right, so the lattice of (i, j) will only come from (i - 1, j) or (i, j - 1) (regardless of boundary conditions). Note the relationship between or.

class Solution {
    boolean[][] board;
    public int movingCount(int m, int n, int k) {
        board=new boolean[m][n];
        int result=1;
        board[0][0]=true;
        for(int i=0;i<m;++i){
            for(int j=0;j<n;++j){
                if((i==0&&j==0)||(countNum(i)+countNum(j)>k)){
                    continue;
                }
                if(i>0){
                    board[i][j]|=board[i-1][j];
                }
                if(j>0){
                    board[i][j]|=board[i][j-1];
                }
                if(board[i][j]){
                    ++result;
                }
            }
        }
        return result;
    }

    public int countNum(int num){
        int sum=0;
        while(num!=0){
            int temp=num%10;
            sum+=temp;
            num/=10;
        }
        return sum;
    }
}

 

Example 5: Arrangement of strings

Example 2: character version of full arrangement II

Solution 1

class Solution {
    List<String> result;
    public String[] permutation(String s) {
        result=new ArrayList<String>();
        char[] c=s.toCharArray();
        dfs(0,c);
        return result.toArray(new String[result.size()]);
    }
    //The dfs function is used to determine the value of c[index]
    void dfs(int index,char[] c){
        if(index==c.length){
            result.add(String.valueOf(c));
        }
        Set<Character> set=new HashSet<>();
        for(int i=index;i<c.length;++i){
            //The previous step is to determine that the optional subscript range of c[index] is [index,c.length)
            //This pruning is to ensure that c[index] does not jump to the next number as the previously selected value
            if(set.contains(c[i])){
                continue;
            }
            //When c[i] is not the same as the previously selected value, it is added to the set and the positions of c[index] and c[i] are exchanged
            set.add(c[i]);
            swap(c,i,index);
            //Then determine the next position index+1
            dfs(index+1,c);
            swap(c,i,index);
        }
    }
    void swap(char[] c,int index1,int index2){
        char temp=c[index1];
        c[index1]=c[index2];
        c[index2]=temp;
    }

}

Solution 2

class Solution {
    List<String> result;
    public String[] permutation(String s) {
        result=new ArrayList<String>();
        char[] c=s.toCharArray();
        boolean[] visited=new boolean[s.length()];
        Arrays.sort(c);
        StringBuilder temp=new StringBuilder("");
        dfs(0,c,temp,visited);
        return result.toArray(new String[result.size()]);
    }
    void dfs(int index,char[] c,StringBuilder temp,boolean[] visited){
        if(index==c.length){
            result.add(temp.toString());
        }
        for(int i=0;i<c.length;++i){
            if(visited[i]){
                continue;
            }
            if(i>0&&c[i]==c[i-1]&&visited[i-1]==false){
                continue;
            }
            temp.append(c[i]);
            visited[i]=true;
            dfs(index+1,c,temp,visited);
            temp.deleteCharAt(temp.length()-1);
            visited[i]=false;
        }
    }

}

 

Example 6: Next arrangement

The idea is as follows:
We need to exchange a "smaller number" on the left with a "larger number" on the right to make the current arrangement larger and get the next arrangement.
At the same time, we should keep the "smaller number" to the right and the "larger number" as small as possible. When the exchange is completed, the numbers to the right of the "larger number" need to be rearranged in ascending order. In this way, the amplitude of enlargement can be reduced as much as possible while ensuring that the new arrangement is larger than the original arrangement.
The specific implementation is as follows:
1 first, find the first sequence pair (i,i+1) from back to front to meet a[i] < a[i + 1]. In this way, the "smaller fraction" is a[i]. At this time [i+1,n) must be a descending sequence.
2 if the sequence pair is found, the first element jj is found from back to front in the interval [i+1,n), which satisfies a [i] < a[j]. In this way, the "larger number" is a[j].
3 exchange a[i] and a[j], and it can be proved that the interval [i+1,n) must be in descending order. We can directly use double pointers to reverse the interval [i+1,n) to make it in ascending order without sorting the interval.

class Solution {
    public void nextPermutation(int[] nums) {
        int smallNumIndex=-1;
        int bigNumIndex=-1;
        //Find the "smaller decimal" as far to the right as possible
        for(int i=nums.length-2;i>=0;--i){
            if(nums[i]<nums[i+1]){
                smallNumIndex=i;
                break;
            }
        }
        //Find the "larger number" as small as possible and to the right of the "smaller number"
        if (smallNumIndex >= 0) {       //If it is already the largest sequence, reverse it directly
            for(int i=nums.length-1;i>smallNumIndex;--i){
                if(nums[i]>nums[smallNumIndex]){
                    bigNumIndex=i;
                    break;
                }
            }
            swap(nums,smallNumIndex,bigNumIndex);
        }
        reverse(nums,smallNumIndex+1);
    }
    public void swap(int[] nums,int index1,int index2){
        int temp=nums[index1];
        nums[index1]=nums[index2];
        nums[index2]=temp;
    }
    public void reverse(int[] nums,int start){
        int left=start,right=nums.length-1;
        while(left<right){
            swap(nums,left,right);
            left++;
            right--;
        }
    }
}

This question also brings understanding method 3 to example 2 and example 5
Solution 3 of example 2:

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> result=new ArrayList<>();
        Arrays.sort(nums);
        do{
            List<Integer> temp=new ArrayList<>();
            for(int i=0;i<nums.length;++i){
                temp.add(nums[i]);
            }
            result.add(temp);
        }while(nextPermute(nums));
        return result;
    }
    public boolean nextPermute(int[] nums){
        int smallNumIndex=-1;
        int bigNumIndex=-1;
        for(int i=nums.length-2;i>=0;--i){
            if(nums[i]<nums[i+1]){
                smallNumIndex=i;
                break;
            }
        }
        if(smallNumIndex==-1){
            return false;
        }
        for(int i=nums.length-1;i>smallNumIndex;--i){
            if(nums[i]>nums[smallNumIndex]){
                bigNumIndex=i;
                break;
            }
        }
        swap(nums,smallNumIndex,bigNumIndex);
        reverse(nums,smallNumIndex+1);
        return true;
    }
    public void swap(int[] nums,int index1,int index2){
        int temp=nums[index1];
        nums[index1]=nums[index2];
        nums[index2]=temp;
    }
    public void reverse(int[] nums,int index){
        int left=index,right=nums.length-1;
        while(left<right){
            swap(nums,left,right);
            ++left;
            --right;
        }
    }
}

Solution 3 of example 5:

class Solution {
    public String[] permutation(String s) {
        List<String> ret = new ArrayList<String>();
        char[] arr = s.toCharArray();
        Arrays.sort(arr);
        do {
            ret.add(new String(arr));
        } while (nextPermutation(arr));
        return ret.toArray(new String[ret.size()]);
    }

    public boolean nextPermutation(char[] arr) {
        int i = arr.length - 2;
        while (i >= 0 && arr[i] >= arr[i + 1]) {
            i--;
        }
        if (i < 0) {
            return false;
        }
        int j = arr.length - 1;
        while (j >= 0 && arr[i] >= arr[j]) {
            j--;
        }
        swap(arr, i, j);
        reverse(arr, i + 1);
        return true;
    }

    public void swap(char[] arr, int i, int j) {
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public void reverse(char[] arr, int start) {
        int left = start, right = arr.length - 1;
        while (left < right) {
            swap(arr, left, right);
            left++;
            right--;
        }
    }
}

 

Example 7: Permutation sequence

Solution 1:

If you want to do it as a derivative of example 1, you must timeout. It's OK to do it as a derivative of example 6

class Solution {
    public String getPermutation(int n, int k) {
        int[] nums=new int[n];
        for(int i=0;i<n;++i){
            nums[i]=i+1;
        }
        int count=1;
        while(count<k&&nextPermute(nums)){
            ++count;
        }
        StringBuilder result=new StringBuilder();
        for(int i=0;i<n;++i){
            result.append((char)(nums[i]+'0'));
        }
        return result.toString();
    }

    public boolean nextPermute(int[] nums){
        int smallNumIndex=-1;
        int bigNumIndex=-1;
        for(int i=nums.length-2;i>=0;--i){
            if(nums[i]<nums[i+1]){
                smallNumIndex=i;
                break;
            }
        }
        if(smallNumIndex==-1){
            return false;
        }
        for(int i=nums.length-1;i>smallNumIndex;--i){
            if(nums[i]>nums[smallNumIndex]){
                bigNumIndex=i;
                break;
            }
        }
        swap(nums,smallNumIndex,bigNumIndex);
        reverse(nums,smallNumIndex+1);
        return true;
    }
    public void swap(int[] nums,int index1,int index2){
        int temp=nums[index1];
        nums[index1]=nums[index2];
        nums[index2]=temp;
    }
    public void reverse(int[] nums,int index){
        int left=index,right=nums.length-1;
        while(left<right){
            swap(nums,left,right);
            ++left;
            --right;
        }
    }

}

Solution 2:

There are mathematical methods to do it. For me, it is basically understanding Big brother problem solution , and then memorize it and keep silent during the interview. But I don't understand yet

Topics: Algorithm data structure Interview