剑指offer26-30
26 字符串的排列
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
思路:将当前位置的字符和前一个字符位置交换,递归。
public ArrayList<String> Permutation(String str) {
ArrayList<String> result = new ArrayList<String>();
if (str == null || str.length() == 0)
return result;
char[] chars = str.toCharArray();
TreeSet<String> temp = new TreeSet<>();
Permutation(chars, 0, temp);
result.addAll(temp);
return result;
}
public void Permutation(char[] chars, int index, TreeSet<String> result) {
if (chars == null || chars.length == 0)
return;
if (index < 0 || index > chars.length - 1)
return;
if (index == chars.length - 1) {
result.add(String.valueOf(chars));
} else {
for (int i = index; i <= chars.length - 1; i++) {
swap(chars, index, i);
Permutation(chars, index + 1, result);
// 回退
swap(chars, index, i);
}
}
}
public void swap(char[] c, int a, int b) {
char temp = c[a];
c[a] = c[b];
c[b] = temp;
}
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
public class arrangementOfString_26 {
public static void main(String[] args) {
arrangementOfString_26 res = new arrangementOfString_26();
String str = "abc";
ArrayList<String> result = res.Permutation(str);
System.out.println(result.toString());
}
Set<String> set = new TreeSet<>();
ArrayList<String> list = new ArrayList<String>();
public ArrayList<String> Permutation(String str) {
String s = "";
Set<String> set = combination(s, str.toCharArray(),str);
for(String s1 : set) {
list.add(s1);
}
return list;
}
public Set<String> combination(String s,char[] c,String str){
for(int i = 0; i < c.length; i++){
char[] temp = new char[c.length-1];
int h = 0;
for(int j = 0; j < c.length; j++) {
if(j != i) {
temp[h] = c[j];
h++;
}
}
combination(s+c[i],temp,str);
if((s+c[i]).length() == str.length())
set.add(s+c[i]);
}
return set;
}
}
27数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
import java.util.HashMap;
import java.util.Map;
public class MoreThanHalfNum_27 {
public static void main(String[] args) {
int[] array = {1,2,3,2,2,2,5,4,2};
MoreThanHalfNum_27 find = new MoreThanHalfNum_27();
int res = find.MoreThanHalfNum_Solution(array);
System.out.println(res);
}
public int MoreThanHalfNum_Solution(int[] array) {
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0; i < array.length; i++) {
map.put(array[i],map.getOrDefault(array[i],0)+1);
}
for(Map.Entry<Integer, Integer>entry:map.entrySet()) {
if(entry.getValue() > array.length / 2) {
return entry.getKey();
}
}
return 0;
}
}
28 最小的k个数
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
直接通过快排切分排好第 K 小的数(下标为 K-1),那么它左边的数就是比它小的另外 K-1 个数啦~
我们的目的是寻找最小的 k个数。假设经过一次 partition 操作,枢纽元素位于下标 j,也就是说,左侧的数组有 j 个元素,是原数组中最小的 j个数。那么:
若 k=j,我们就找到了最小的 k 个数,就是左侧的数组;
若 k<j ,则最小的 k 个数一定都在左侧数组中,我们只需要对左侧数组递归地 parition 即可;
若 k>j,则左侧数组中的 j 个数都属于最小的 k 个数,我们还需要在右侧数组中寻找最小的 k−j 个数,对右侧数组递归地 partition 即可。
快排切分时间复杂度分析: 因为我们是要找下标为k的元素,第一次切分的时候需要遍历整个数组 (0 ~ n) 找到了下标是 j 的元素,假如 k 比 j 小的话,那么我们下次切分只要遍历数组 (0~k-1)的元素就行啦,反之如果 k 比 j 大的话,那下次切分只要遍历数组 (k+1~n) 的元素就行啦,总之可以看作每次调用 partition 遍历的元素数目都是上一次遍历的 1/2,因此时间复杂度是 N + N/2 + N/4 + ... + N/N = 2N, 因此时间复杂度是 O(N)。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0) {
return new int[0];
}
// 最后一个参数表示我们要找的是下标为k-1的数
return quickSearch(arr, 0, arr.length - 1, k - 1);
}
private int[] quickSearch(int[] nums, int lo, int hi, int k) {
// 每快排切分1次,找到排序后下标为j的元素,如果j恰好等于k就返回j以及j左边所有的数;
int j = partition(nums, lo, hi);
if (j == k) {
return Arrays.copyOf(nums, j + 1);
}
// 否则根据下标j与k的大小关系来决定继续切分左段还是右段。
return j > k? quickSearch(nums, lo, j - 1, k): quickSearch(nums, j + 1, hi, k);
}
// 快排切分,返回下标j,使得比nums[j]小的数都在j的左边,比nums[j]大的数都在j的右边。
private int partition(int[] nums, int lo, int hi) {
int v = nums[lo];
int i = lo, j = hi + 1;
while (true) {
while (++i <= hi && nums[i] < v);
while (--j >= lo && nums[j] > v);
if (i >= j) {
break;
}
int t = nums[j];
nums[j] = nums[i];
nums[i] = t;
}
nums[lo] = nums[j];
nums[j] = v;
return j;
}
}
大顶堆
import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
if (input == null || k <= 0 || k > input.length) {
return list;
}
int[] kArray = Arrays.copyOfRange(input, 0, k);
// 创建大根堆
buildHeap(kArray);
for (int i = k; i < input.length; i++) {
if (input[i] < kArray[0]) {
kArray[0] = input[i];
maxHeap(kArray, 0);
}
}
for (int i = kArray.length - 1; i >= 0; i--) {
list.add(kArray[i]);
}
return list;
}
public void buildHeap(int[] input) {
for (int i = input.length / 2 - 1; i >= 0; i--) {
maxHeap(input, i);
}
}
private void maxHeap(int[] array, int i) {
int left = 2 * i + 1;
int right = left + 1;
int largest = 0;
if (left < array.length && array[left] > array[i])
largest = left;
else
largest = i;
if (right < array.length && array[right] > array[largest])
largest = right;
if (largest != i) {
int temp = array[i];
array[i] = array[largest];
array[largest] = temp;
maxHeap(array, largest);
}
}
}
import java.util.ArrayList;
import java.util.Arrays;
public class GetLeastNumbers_28 {
public static void main(String[] args) {
GetLeastNumbers_28 min = new GetLeastNumbers_28();
int[] input = {4,5,1,6,2,7,3,8};
int k =10;
ArrayList<Integer> res = min.GetLeastNumbers_Solution(input, k);
System.out.println(res.toString());
}
ArrayList<Integer> list = new ArrayList<Integer>();
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input,int k){
if(k > input.length) {
return list;
}
Arrays.sort(input);
for(int i = 0; i < k; i++) {
list.add(input[i]);
}
return list;
}
}
29连续数组的最大和
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
典型的动态规划。dp[n]代表以当前元素为截止点的连续子序列的最大和,如果dp[n-1]>0,dp[n]=dp[n]+dp[n-1],因为当前数字加上一个正数一定会变大;如果dp[n-1]<0,dp[n]不变,因为当前数字加上一个负数一定会变小。使用一个变量max记录最大的dp值返回即可。
public class FindGreatestSumOfSubArray_29 {
public static void main(String[] args) {
FindGreatestSumOfSubArray_29 max = new FindGreatestSumOfSubArray_29();
int[] array = {6,-3,-2,7,-15,1,2,2};
int res = max.FindGreatestSumOfSubArray(array);
System.out.println(res);
}
public int FindGreatestSumOfSubArray(int[] array) {
int maxSum = array[0];
for(int i =1; i < array.length; i++) {
array[i] += array[i-1] > 0 ? array[i-1] : 0;
maxSum = Math.max(maxSum, array[i]);
}
return maxSum;
}
}
30整数中1出现的次数 ***
求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
public class NumberOf1Between1AndN_30 {
public static void main(String[] args) {
NumberOf1Between1AndN_30 num = new NumberOf1Between1AndN_30();
int res = num.NumberOf1Between1AndN_Solution(216);
System.out.println(res);
}
public int NumberOf1Between1AndN_Solution(int n) {
int count = 0;
for(int m = 1; m < n; m *= 10) {
int a = n / m; int b = n % m;
count += (a + 8) / 10 *m + (a % 10 == 1 ? b+1 : 0);
}
return count;
}
}
进口的java.util.ArrayList;
进口java.util.Arrays中;
公共类解决方案{
公共的ArrayList <Integer>的GetLeastNumbers_Solution(INT []输入,INT K){
的ArrayList <Integer>的列表=新的ArrayList <>();
如果(输入== NULL ||ķ<= 0 || K> input.length){
返回列表;
}
INT [] kArray = Arrays.copyOfRange(输入,0,K);
//创建大根堆
buildHeap(kArray);
对(INT I = K;我<input.length;我++){
如果(输入[I] <kArray [0]){
kArray [0] =输入[I];
maxHeap(kArray,0);
}
}
对(INT I = kArray.length - 1; I> = 0;我 - ){
list.add(kArray [I]);
}
返回列表;
}
公共无效buildHeap(INT []输入){
对(INT I = input.length / 2 - 1; I> = 0;我 - ){
maxHeap(输入,I);
}
}
私人无效maxHeap(INT []数组,int i)以{
INT左= 2 * I + 1;
诠释右向左= + 1;
INT最大= 0;
如果(左<array.length &&阵列[左]>阵列[I])
最大=左;
其他
最大= I;
如果(右<array.length &&阵列[右]>阵列[最大])
最大=权利;
如果(最大!= 1){
INT温度=阵列[I];
阵列[I] =阵列[最大];
阵列[最大] =温度;
maxHeap(数组,最大);
}
}
}