【算法设计与分析(课后答案)】二分

1. 求解满足条件的元素对个数问题

【问题描述】 给定N个整数Ai以及一个正整数C,问其中有多少对i、j满足Ai-Aj=C。

【输入描述】第1行输入两个空格隔开的整数N和C,第2~N+1行每行包含一个整数Ai。
【输出描述】输出一个数表示答案。

【输入样例】5 3
         2
         1
         4
         2
         5
【输出样例】3

public class Solution1 {
    
    

    /**
     * 解法一:先排序,然后双指针遍历
     * 时间复杂度O(logn)+O(n^2)
     */
    public int countElementPairs(int[] nums, int C) {
    
    
        int len = nums.length;
        int cnt = 0;
        Arrays.sort(nums);
        System.out.println(Arrays.toString(nums));

        for (int i = 0; i < len; i++) {
    
    
            for (int j = i + 1; j < len; j++) {
    
    
                if (nums[j] - nums[i] == C) {
    
    
                    cnt++;
                } else if (nums[j] - nums[i] > C) {
    
    
                    break;
                }
            }
        }
        return cnt;
    }


    /**
     * 解法二:先排序,然后用二分法找nums[i]+C
     * 时间复杂度O(logn)+O(nlogn)
     */
    public int countElementPairs2(int[] nums, int C) {
    
    
        int len = nums.length;
        int cnt = 0;
        Arrays.sort(nums);
        for (int i = 0; i < len; i++) {
    
    
            cnt += countTarget(nums, nums[i] + C);
        }
        return cnt;
    }

    // 统计有序数组中target的个数
    private int countTarget(int[] nums, int target) {
    
    
        int borderL = serchLeftBorder(nums, target);
        int borderR = serchRightBorder(nums, target);
        if (borderL != -1 && borderR != -1) {
    
    
            return borderR - borderL + 1;
        } else {
    
    
            return 0;
        }
    }

    // 找有序数组中target的最左下标
    private int serchLeftBorder(int[] nums, int target) {
    
    
        int len = nums.length;
        int left = 0;
        int right = len - 1;
        int mid = 0;
        while (left <= right) {
    
    
            mid = (left + right) / 2;
            if (nums[mid] == target) {
    
    
                if (mid == 0 || nums[mid - 1] != target) {
    
    
                    return mid;
                } else {
    
    
                    right = mid - 1;
                }
            } else if (nums[mid] < target) {
    
    
                left = mid + 1;
            } else {
    
    
                right = mid - 1;
            }
        }
        return -1;
    }

    // 找有序数组中target的最右下标
    private int serchRightBorder(int[] nums, int target) {
    
    
        int len = nums.length;
        int left = 0;
        int right = len - 1;
        int mid = 0;
        while (left <= right) {
    
    
            mid = (left + right) / 2;
            if (nums[mid] == target) {
    
    
                if (mid == len - 1 || nums[mid + 1] != target) {
    
    
                    return mid;
                } else {
    
    
                    left = mid + 1;
                }
            } else if (nums[mid] < target) {
    
    
                left = mid + 1;
            } else {
    
    
                right = mid - 1;
            }
        }
        return -1;
    }


    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int C = scanner.nextInt();
        int[] nums = new int[n];
        for (int i = 0; i < n; i++) {
    
    
            nums[i] = scanner.nextInt();
        }
        System.out.println(new Solution1().countElementPairs2(nums, C));
    }
}

 
 


2. 求解查找最后一个小于等于指定的元素问题

【问题描述】 给一个长度为n的单调递增的正整数序列,即序列中每一个数都比前一个数大。有m个询问,每次询问一个x,问序列中最后一个小于等于x的数是什么?

【输入描述】第一行两个整数n,m。接下来一行n个数,表示这个序列。接下来m行每行一个数,表示一个询问。
【输出描述】输出共m行,表示序列中最后一个小于等于x的数是什么。假如没有,则输出-1。

【输入样例】5 3
         1 2 3 4 6
         5
         1
         3
【输出样例】4
         1
         3

public class Solution2 {
    
    

    /**
     * 本题与Java的Arrays.binarySearch的源码实现,有异曲同工之妙。
     * 只要我们能理解为什么二分查找是安全的,那么理解下面的代码就会非常简单。
     *
     * 那么问自己一句,二分查找安全性的关键在于何处?
     * 在于无论什么时候,num[min]<=target,num[max]>=target。
     *
     * 在一个递增(不重复)数组中,二分查找一个不存在的数,必然会打破上面的安全性,即出现一次"反常"———num[min]>target,num[max]<target
     * 当出现这次反常后,之后的二分便不再具有意义
     * 同时我们是可以保证的,目标值(target)就出现在第一次反常之处:
     *      if(num[min]>target)即左侧反常,target的值最接近于 nums[left - 1] 或者 nums[left]
     *      if(num[max]<target)即右侧反常,target的值最接近于 nums[right] 或者 nums[right + 1]
     *      
     *      
     * 回到本题:
     * 本题需要取左,因此不管target靠近谁,一律取小值: nums[left - 1] 或者 nums[right]
     * 
     * 给两个极其极其好的例子:
     * [1, 3, 4, 6] search 5
     * [1, 3, 4, 6] search 2
     *
     */
    public int findNum(int[] nums, int target) {
    
    
        int left = 0;
        int right = nums.length - 1;
        int mid = 0;
        while (left <= right) {
    
    
            mid = (left + right) / 2;
            if (nums[left] > target) {
    
    
                if (left - 1 < 0) {
    
    
                    return -1;
                }
                return nums[left - 1];
            }
            if (nums[right] < target) {
    
    
                return nums[right];
            }
            if (nums[mid] == target) {
    
    
                return nums[mid];
            } else if (nums[mid] < target) {
    
    
                left = mid + 1;
            } else if (nums[mid] > target) {
    
    
                right = mid - 1;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        // 数组的长度
        int len1 = scanner.nextInt();
        // 输入的长度
        int len2 = scanner.nextInt();
        int[] nums = new int[len1];
        for (int i = 0; i < len1; i++) {
    
    
            nums[i] = scanner.nextInt();
        }
        int[] res = new int[len2];
        Solution2 solution2 = new Solution2();
        for (int i = 0; i < len2; i++) {
    
    
            res[i] = solution2.findNum(nums, scanner.nextInt());
        }
        for (int n : res) {
    
    
            System.out.println(n);
        }
    }
}

 
 


3. 求解递增序列中与x最接近的元素问题

【问题描述】 给一个长度为n的单调递增的正整数序列,即序列中每一个数都比前一个数大。有m个询问,每次询问一个x,问序列中最后一个小于等于x的数是什么?

【输入描述】第一行两个整数n,m。接下来一行n个数,表示这个序列。接下来m行每行一个数,表示一个询问。
【输出描述】输出共m行,表示序列中最后一个小于等于x的数是什么。假如没有,则输出-1。

【输入样例】5 3
         1 2 3 4 6
         5
         1
         3
【输出样例】4
         1
         3

public class Solution3 {
    
    

	/**
     * 关键还是要理解二分法的安全性,以及打破这种安全的"反常"
     * 
     */
    public int findNearest(int[] nums, int target) {
    
    
        int len = nums.length;
        if (nums[0] > target) {
    
    
            return nums[0];
        }
        if (nums[len - 1] < target) {
    
    
            return nums[len - 1];
        }
        int left = 0;
        int right = len - 1;
        int mid = 0;
        while (left <= right) {
    
    
            mid = (left + right) / 2;
            if (nums[left] > target) {
    
    
                return target - nums[left - 1] <= nums[left] - target ? nums[left - 1] : nums[left];
            }
            if (nums[right] < target) {
    
    
                return target - nums[right] <= nums[right + 1] - target ? nums[right] : nums[right + 1];
            }
            if (nums[mid] == target) {
    
    
                return nums[mid];
            } else if (nums[mid] < target) {
    
    
                left = mid + 1;
            } else if (nums[mid] > target) {
    
    
                right = mid - 1;
            }
        }
        return -1;
    }
    

    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        // 数组的长度
        int len1 = scanner.nextInt();
        int[] nums = new int[len1];
        for (int i = 0; i < len1; i++) {
    
    
            nums[i] = scanner.nextInt();
        }
        // 输入的长度
        int len2 = scanner.nextInt();
        int[] res = new int[len2];
        Solution3 solution3 = new Solution3();
        for (int i = 0; i < len2; i++) {
    
    
            res[i] = solution3.findNearest(nums, scanner.nextInt());
        }
        for (int n : res) {
    
    
            System.out.println(n);
        }
    }
}

 
 


4. 求解按"最多排序"到"最少排序"的顺序排列问题

【问题描述】 一个序列中的“未排序”的度量是相对于彼此顺序不一致的条目对的数量,例如,在字母序列“DAABEC"中该度量为5,因为D大于其右边的4个字母,E大于其右边的1个字母。该度量称为该序列的逆序数。序列“AACEDGG”只有一个逆序对(E和D),它几乎被排好序了,而序列“ZWQM"有6个逆序对,它是未排序的,恰好是反序。需要对若干个DNA序列(仅包含4个字母A、C、G和T的字符串)分类,注意是分类而不是按字母顺序排列,是按照“最多排序”到“最小排序”的顺序排列,所有DNA序列的长度都相同。

【输入描述】第1行包含两个整数,n(0≤n≤50)表示字符串长度,m(0<m≤100)表示字符串个数;后面是m行,每行包含一个长度为n的字符串。
【输出描述】按“最多排序”到“最小排序”的顺序输出所有字符串。若两个字符串的逆序对个数相同,按原始顺序输出它们。

【输入样例】10 6
         AACATGAAGG
         TTTTGGCCAA
         TTTGGCCAAA
         GATCAGATTT
         CCCGGGGGGA
         ATCGATGCAT
【输出样例】4
         CCCGGGGGGA
         AACATGAAGG
         GATCAGATTT
         ATCGATGCAT
         TTTTGGCCAA
         TTTGGCCAAA

class DNA {
    
    

    String sequence;    // 序列
    int cost;           // 度量

    public DNA(String sequence) {
    
    
        this.sequence = sequence;
        char[] charArray = sequence.toCharArray();
        int len = charArray.length;
        for (int i = 0; i < len; i++) {
    
    
            for (int j = i + 1; j < len; j++) {
    
    
                cost += (charArray[i] - charArray[j] > 0 ? 1 : 0);
            }
        }
    }

    @Override
    public String toString() {
    
    
        return sequence;
    }
}

public class Solution4 {
    
    
    
    public void sortByCost(List<DNA> dnaList) {
    
    
        // 可以归并排序,但没必要(雾
        Collections.sort(dnaList, Comparator.comparingInt(o -> o.cost));
    }

    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        int dnaLen = scanner.nextInt();
        int len = scanner.nextInt();
        List<DNA> dnaList = new ArrayList<>();
        for (int i = 0; i < len; i++) {
    
    
            dnaList.add(new DNA(scanner.next()));
        }
        new Solution4().sortByCost(dnaList);
        for (DNA dna : dnaList) {
    
    
            System.out.println(dna);
        }
    }
}

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

⭐️ 这一定是全网最优美的Java解法 >_<

猜你喜欢

转载自blog.csdn.net/m0_46202073/article/details/115180667