53. 在排序数组中查找数字(看到单调递增或者递减,就应该想到二分查找)
53-1 数字排排序数组中出现的次数
思路:(1)暴力O(n);(2)直接二分O(n):查找第一个k和倒数第一个k的位置。k可能出现n次,时间复杂度还是O(n);(3)特别的二分O(logn)
class Solution {
public int getNumberOfK(int[] nums, int k) {
if(nums==null|| nums.length==0) return 0;//长度为0
int first = getFirst(nums, 0, nums.length-1, k);//第一个
int last = getLast(nums, 0, nums.length-1, k);//最后一个
if(first>-1&&last>-1)
return last-first+1;//存在
return 0;
}
public int getFirst(int[] nums, int left, int right, int k){
if(left>right) return -1;
int m = (left+right)>>1;
if(nums[m]>k) right = m-1;//中间的数不是第一个
else if(nums[m]<k) left = m+1;
else{
if(m==0||(m>0 && nums[m-1]!=k)){//到头了或者中间的数的前一个不等于中间的数
return m;
}
else right = m-1;//等于
}
return getFirst(nums, left, right, k);
}
public int getLast(int[] nums, int left, int right, int k){
if(left>right) return -1;
int m = (left+right)>>1;
if(nums[m]>k) right = m-1;
else if(nums[m]<k) left = m+1;
else{
if(m==nums.length-1 || (m<nums.length-1 && nums[m+1]!=k)){
return m;
}
else left = m+1;
}
return getLast(nums, left, right, k);
}
}
53-2 0-n-1中缺失的数字
思路:(1)先计算总和,再计算数组中数字的总和,再相减,复杂度O(n);(2)转化为:找出数组中第一个下标与对应值不同的元素的下标。(蕴含着该数前一个等于前一个的下标)
class Solution {
public int getMissingNumber(int[] nums) {
if(nums==null || nums.length==0) return 0;
int left=0, right = nums.length-1;
if(nums[right]==right) return right+1;//如果缺失的是最后一个数字
while(left<=right){
if(left<0 || right>nums.length-1) return -1;//结束条件
int mid = (left+right)>>1;
if(nums[mid]==mid){
left = mid+1;
}
else{
if(mid==0 || nums[mid-1]==mid-1){//第一个或者前一个相等,返回当下这个
return mid;
}
right = mid-1;
}
}
return -1;
}
}
53-3 数组中数值和下标相等的元素
class Solution {
public int getNumberSameAsIndex(int[] nums) {
if(nums==null || nums.length==0) return -1;
if(nums[nums.length-1] < nums.length-1) return -1;
int left = 0, right = nums.length-1;
while(left<=right){
int mid = (left+right)>>1;
if(mid==nums[mid]) return mid;
else if(nums[mid]<mid) left=mid+1;
else right = mid-1;
}
return -1;
}
}
54. 二叉搜索树的第k大个节点
思路:中序遍历
class Solution {
int count=0;
TreeNode ans=null;
public TreeNode kthNode(TreeNode root, int k) {
countK(root, k);
return ans;
}
public void countK(TreeNode root, int k){
if(root==null||count>=k){
return;
}
countK(root.left,k);
count++;
if(count==k){
ans=root;
}
countK(root.right,k);
}
}
55. 二叉树的深度
55-1 二叉树的深度
思路:如果一棵树只有一个节点那么它的深度是1;如果有子树,应该是左子树和右子树中,深度最大的子树的深度+1就是,整棵树的深度。时间复杂度O(n)。
class Solution {
int count=0;
public int treeDepth(TreeNode root) {
if(root==null) return 0;
else{
return 1+Math.max(treeDepth(root.left), treeDepth(root.right));
}
}
}
55-2 平衡二叉树
思路:每个节点的左右子树的深度相差都不超过一。还有一种方法是后序遍历,再看到的时候再写吧。
class Solution {
public boolean isBalanced(TreeNode root) {
if(root==null) return true;
int left = TreeDepth(root.left);
int right = TreeDepth(root.right);
int diff = right-left;
if(diff>1 || diff<-1) return false;
return isBalanced(root.left)&&isBalanced(root.right);//左右子树深度相差不超过一
}
public int TreeDepth(TreeNode root){//
if(root==null) return 0;
int left = TreeDepth(root.left);
int right = TreeDepth(root.right);
return (left>right)?(left+1):(right+1);
}
}
56. 数组中数字出现的次数
56-1 数组中只出现一次的两个数字
思路:(1)先考虑找出数组中子会出现一次的一个数字。考虑到每个数字与自己做异或运算都等于0。所以整个数组一一进行异或,得到得就是这个数字。(2)下面是这个题的思路:(异或运算) O(n)
1. 首先遍历整个数组,对整个数组的每个值求异或。得到的结果是只出现一次的两个数的异或结果,因为这两个值是不相同的,所以异或的结果不可能为零,所以一定有一位是1,也就是一定有一位这两个值不相同。那么按这一位,将整个数组分成两部分,然后对两个数组分别进行异或遍历,就可以得到只出现一次的两个值。找到两个数不同的那一位:找到倒数第一位为1 的位置,return。
class Solution {
public int[] findNumsAppearOnce(int[] nums) {
int res=0;
for(int i=0; i<nums.length; i++){
res = res^nums[i];
}
int index = getFirstBit(res);//倒数第一个不同的位置
int num1=0, num2=0;
for(int i=0; i<nums.length; i++){//分组,这位为1为一组,为0为第二组
if(((nums[i]>>(index-1))&1)==1) num1 = num1^nums[i];
else num2 = num2^nums[i];
}
return new int[]{num1, num2};
}
public int getFirstBit(int res){
int place=1;
while(res!=0 && (res&1)!=1){
res = res>>1;
place++;
}
return place;
}
}
56-2 数组中唯一只出现一次的数字
思路:和上一题一样,往异或方向上想没思路。这题和异就没关系了,int一共是32位,新建一个各位和的数组,我们把每个数字对应的1和0加到这个数组的对应位置上,比如1,1,1相加就变成了003,再加上2,2,2就是033,再加上4,4,4就是333,再加上1,就是334,加完后,如果某一位上是3的倍数,说明目标数字这一位上是0,如果不是3的倍数,那么这一位上就是1
时间是0(n),第二层的循环因为是常数32,所以是O(1),空间的话开了常数长度的数组也是0(1)
class Solution {
public int findNumberAppearingOnce(int[] nums) {
int[] sum = new int[32];
for(int i=0; i<nums.length; i++){
for(int j=0; j<32; j++){
sum[j] += (nums[i]>>j) &1;
}
}
int res=0;
for(int i=0;i<32;i++){
res+=(sum[i]%3==0)?0:1<<i;
}
return res;
}
}
57. 和为s的数字
57-1 和为s的两个数字
思路:(1)暴力O(n²);(2)先排序,在用双指针,一个从前一个从后。排序复杂度O(nlogn),循环复杂度O(n)。
class Solution {
public int[] findNumbersWithSum(int[] nums, int target) {
if(nums==null||nums.length==1) return null;
Arrays.sort(nums);
int left=0, right=nums.length-1;
int num1=0,num2=0;
boolean find = false;
while(left<=right){
if(nums[left]+nums[right]==target){
num1 = nums[left];
num2 = nums[right];
find = true;
break;
}
else if(nums[left]+nums[right]<target) left+=1;
else right-=1;
}
if(find) return new int[]{num1, num2} ;
else return null;
}
}
57-2 和为s的连续正数序列
思路:(双指针) O(n),大小指针分别指向2,1。当大小指针中间的数字序列之和大于给定的值,小指针向前移动
如果小于给定的值,大指针向后移动。当小指针大于给定值的二分之一,后面的值相加一定会大于给定的值,所以,循环结束条件就是当小指针小于给定值的一半。
class Solution {
public List<List<Integer> > findContinuousSequence(int sum) {
List<List<Integer>> ans = new ArrayList<>();
int small = 1, big=2;
int curSum = small+big;
while(small<(sum>>1)+1){
if(curSum == sum){
List<Integer> tmp = new ArrayList<>();
for(int i=small; i<=big; i++){
tmp.add(i);
}
ans.add(tmp);
big++;
curSum+=big;
}
else if(curSum < sum){
big++;
curSum += big;
}
else{
curSum -= small;
small++;
}
}
return ans;
}
}
58. 翻转字符串
58-1 翻转单词序列
思路:先全部翻转。再碰到空格翻转前面的单词。
class Solution {
public String reverseWords(String s) {
if(s.length()==0) return s;
char[] allchar = s.toCharArray();//注意函数调用
int start = 0, end = allchar.length-1;
allchar = reverse(allchar, start, end);
start = 0;
end = 0;
while(end<allchar.length){
if(allchar[end]==' '){
allchar = reverse(allchar, start, end-1);
start = end+1;
end = end+1;
}
else if(end==allchar.length-1){
allchar = reverse(allchar, start, end);
break;
}
else{
end++;
}
}
return new String(allchar);//注意char[]转string
}
public char[] reverse(char[] allchar, int start, int end){
for(int i=0; i<=(end-start)>>1; i++){
char tmp = allchar[start+i];
allchar[start+i] = allchar[end-i];
allchar[end-i] = tmp;
}
return allchar;
}
}
58-2 左旋转字符串
思路:abcdef;n=2;翻转三次(1)bacdef(2)bafedc(3)cdefab
class Solution {
public String leftRotateString(String str,int n) {
if(str.length()==0) return str;
char[] allchar = str.toCharArray();
allchar = reverse(allchar, 0, n-1);
allchar = reverse(allchar, n, str.length()-1);
allchar = reverse(allchar, 0, str.length()-1);
return new String(allchar);
}
public char[] reverse(char[] charall, int start, int end){
for(int i=0; i<=(end-start)>>1; i++){
char tmp = charall[start+i];
charall[start+i] = charall[end-i];
charall[end-i] = tmp;
}
return charall;
}
}
59. 队列的最大值
59-1 滑动窗口的最大值
思路:单调递减队列,时间复杂度O(n)。我们维护一个双向单调队列,队列放的是元素的下标。我们假设该双端队列的队头是整个队列的最大元素所在下标,至队尾下标代表的元素值依次降低。初始时单调队列为空。随着对数组的遍历过程中,每次插入元素前,首先需要看队头是否还能留在队列中,如果队头下标距离i超过了k,则应该出队。同时需要维护队列的单调性,如果nums[i]大于或等于队尾元素下标所对应的值,则当前队尾再也不可能充当某个滑动窗口的最大值了,故需要队尾出队。始终保持队中元素从队头到队尾单调递减。依次遍历一遍数组,每次队头就是每个滑动窗口的最大值所在下标。
时间复杂度分析:每个元素最多入队出队一次,复杂度为O(n)
class Solution {
public int[] maxInWindows(int[] nums, int k) {
int[] ans = new int[nums.length-k+1];
ArrayDeque<Integer> q= new ArrayDeque<>();//队列里存储下标
int index = 0;
for(int i=0; i<nums.length; i++){
while(!q.isEmpty() && (i-q.peekFirst()>=k)) {//判断队头是否需要出队
q.pollFirst();
}
while(!q.isEmpty() && nums[i]>=nums[q.peekLast()]){//维护队列单调性
q.pollLast();
}
q.add(i);
if(i>=k-1){
ans[index++] = nums[q.peekFirst()];//取队头作为窗口最大元素
}
}
return ans;
}
}
59-2 队列的最大值
思路:
60. n个骰子的点数
思路:动态规划O(n²);数组dp[i][j]表示用i个骰子扔出和为j的可能数,因为第i个骰子可能扔出1-6的点数,则dp[i][j]=dp[i-1][j-1]+dp[i-1][j-2]+dp[i-1][j-3]+dp[i-1][j-4]+dp[i-1][j-5]+dp[i-1][j-6].
class Solution {
public int[] numberOfDice(int n) {
int[][] dp = new int[n+1][6*n+1];//数组存储第i个骰子,和为j的个数
for(int i=1; i<=6;i++){
dp[1][i]=1;
}
for(int i=2; i<=n; i++){
for(int j=i; j<6*n+1 ;j++){
for(int k=1;k<=6&&j-k>=0;k++){
dp[i][j] += dp[i-1][j-k];
}
}
}
int[] ans = new int[5*n+1];
for(int i=n,j=0;i<6*n+1;i++,j++){
ans[j] = dp[n][i];
}
return ans;
}
}
61. 扑克牌中的顺子
class Solution {
public boolean isContinuous(int [] numbers) {
Arrays.sort(numbers);//先排序
int nums0 = 0;
for(int i=0; i<numbers.length; i++){
if(numbers[i]==0) nums0++;
else break;
}
for(int i =nums0; i<numbers.length-1; i++){
if(numbers[i]==numbers[i+1]) return false;
else{
nums0 = nums0-numbers[i+1]+numbers[i]+1;
}
}
return nums0>=0;
}
}
62. 圆圈中最后剩下的数(约瑟夫环)
思路:(1)遍历;时间复杂度O(mn);空间复杂度O(n);(2)递推(寻找相邻两项之间的关系):约瑟夫环,圆圈长度为 n 的解可以看成长度为 n-1 的解再加上报数的长度 m。因为是圆圈,所以最后需要对 n 取余。时间复杂度O(n),空间复杂度O(1)。
class Solution {
public int lastRemaining(int n, int m) {
List<Integer> nums = new LinkedList<>();
for(int i=0; i<n; i++){
nums.add(i);
}
int index=0;
while(nums.size()>1){
for(int i=1;i<m;i++){
index = (index+1)%nums.size();
}
nums.remove(index);
}
return nums.get(0);
}
}
class Solution {
public int lastRemaining(int n, int m) {
if(n==1) return 0;
return (lastRemaining(n-1,m)+m)%n;
}
}
63. 股票的最大利润
思路:记录min和maxDiff。时间复杂度O(N)
class Solution {
public int maxDiff(int[] nums) {
if(nums==null || nums.length==0 || nums.length==1) return 0;
int min = nums[0];
int maxDiff = nums[1]-min;
for(int i=2; i<nums.length; i++){
if(nums[i-1]<min) min = nums[i-1];
int curDiff = nums[i]-min;
if(curDiff>maxDiff) maxDiff = curDiff;
}
return maxDiff<0?0:maxDiff;
}
}
64.求1+2+3+4+。。。+n
思路:短路运算符
class Solution {
public int getSum(int n) {
int ans=n;
boolean b = (n>0)&&(ans += getSum(n-1))>0;
return ans;
}
}
65. 不用加减乘除做加法
思路:位运算
class Solution {
public int add(int num1, int num2) {
int sum;
int carry;
do{
sum = num1^num2;//异或
carry = (num1&num2)<<1;//与运算,左移表示进位
num1 = sum;
num2 = carry;
}while(carry!=0);
return num1;
}
}
66. 构建乘积数组
思路:用一个数组记录A数组中每个位置前面值的乘积,用第二个数组记录A数组中每个位置后面的乘积,然后把每个位置前后乘积相乘即可。时间复杂度O(n)。
class Solution {
public int[] multiply(int[] A) {
if(A.length==0 || A==null) return A;
int[] B = new int[A.length];
B[0] = 1;
for(int i=1; i<A.length; i++){
B[i] = B[i-1]*A[i-1];
}
int last = 1;
for(int j=A.length-2; j>=0; j--){
last = last * A[j+1];
B[j] = B[j]*last;
}
return B;
}
}