剑指offer刷题笔记
n&1
: 判断n是否为奇数
因为n
为奇数时,对应的二进制数最低位一定为1,n&1
的结果就是1
n为偶数时,相应的最低位为0,n&1
的结果就是0,
可以写n&1 ==1
或者写 n%2 == 1
或者写 n%2
\
>>1
等价于 /2
<<1
等价于 *2
二叉树的镜像 (牛客网)
操作给定的二叉树,将其变换为源二叉树的镜像。 |
---|
/**
* 思路:(递归)交换每个树的左右子树
**/
public void Mirror(TreeNode tree) {
if(tree == null){
System.out.println();
}
else if(tree.left == null && tree.right ==null){
System.out.println();
}else{
TreeNode left = tree.left;
tree.left = tree.right;
tree.right = left;
if(tree.left != null){
Mirror(tree.left);
}
if(tree.right!=null){
Mirror(tree.right);
}
System.out.println(tree);
}
}
对称的二叉树
思路:(递归)
对称二叉树从左至右遍历的值,和从右至左遍历的值是相等的。
注意:某一棵子树只有左子树或只有右子树的情况,所以要记录空节点。
/*思路:首先根节点以及其左右子树,左子树的左子树和右子树的右子树相同
* 左子树的右子树和右子树的左子树相同即可,采用递归
* 非递归也可,采用栈或队列存取各级子树根节点
*/
boolean isSymmetrical(TreeNode root){
return isSymmetrical(root,root);
}
boolean isSymmetrical(TreeNode tree1,TreeNode tree2){
if(tree1 == null && tree2 == null){
return true;
}
if((tree1 == null || tree2 == null) || (tree1.val != tree2.val)){
return false;
}
return isSymmetrical(tree1.left,tree2.right) && isSymmetrical(tree1.right, tree2.left);
}
顺时针打印矩阵
注意:两种情况,从右至左可以的前提是up<=down,从下至上的前提是left<=right
import java.util.ArrayList;
public class Solution {
public static ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> result = new ArrayList<Integer>();
if(matrix.length==0){
return result;
}
int n = matrix.length,m = matrix[0].length;
int left = 0,up =0,right = m-1,down = n-1;
while(right>=0 && down>=0 && left <= right && up <= down){
// left - right
for(int i=left;i<=right;i++){
result.add(matrix[up][i]);
}
up++;
// up - down
if(up<=down){
for(int i=up;i<=down;i++){
result.add(matrix[i][right]);
}
}
right--;
// right - left
if(left<=right && up<= down){
for(int i=right;i>=left;i--){
result.add(matrix[down][i]);
}
}
down--;
// down - up
if(up<=down && left <=right){
for(int i=down;i>=up;i--){
result.add(matrix[i][left]);
}
}
left++;
}
return result;
}
}
包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
思路:需要一个辅助栈tmp,里面存储每次push进来的最小值,初始值是第一个。如果后面遇到比当前tmp栈顶元素更小的值,则继续push进去。
tmp的栈顶元素就是当前的最小值。
import java.util.Stack;
public class Solution {
Stack<Integer> s = new Stack<>();
Stack<Integer> tmpStack = new Stack<>();
public void push(int node) {
s.push(node);
if((!tmpStack.isEmpty()&& node < tmpStack.peek()) || tmpStack.isEmpty()){
tmpStack.push(node);
}
}
public void pop() {
int out = s.pop();
if(out == tmpStack.peek()){
tmpStack.pop();
}
}
public int top() {
return s.peek();
}
public int min() {
if(tmpStack.size()>0){
return tmpStack.peek();
}
return -1;
}
}
栈的压入、弹出序列
思路:
以出栈序列的第一个数字first
为界限,把first
前的数据进栈sdata
,first
后的数据放到一个队列squeue
中。
根据入栈、出栈的规律,如果下一个数字data
是sdata
栈顶元素,则直接弹出。
如果data
在剩下准备入栈的数据队列squeue
中,则把剩下的数字push进栈,直到找到data
。
比如:
pushA = {1,2,3,4,5};
popA = {4,5,3,1,2};
那么,first
是 4
sdata
sdata |
---|
3 |
2 |
1 |
squeue
squeue |
---|
4 |
5 |
public static boolean IsPopOrder(int [] pushA,int [] popA) {
Stack<Integer> sdata = new Stack<Integer>();
Queue<Integer> squeue = new LinkedList<>();
int first = popA[0];
int tmp = 0;
for(int i=0;i<pushA.length;i++){
if(pushA[i]==first) tmp=1;
if(tmp ==1)squeue.add(pushA[i]);
else sdata.add(pushA[i]);
}
boolean result = false;
for(int i=0;i<popA.length;i++){
int data = popA[i];
if(sdata.size()>0 && sdata.peek()==data){
sdata.pop();result = true;
}else if(!squeue.isEmpty()){
while(!squeue.isEmpty()){
int q = squeue.poll();
if(q==data){
result = true;break;
}
else result =false;sdata.add(q);
}
}// else
else { result = false;break; }
}// for
// System.out.println(sdata);
// System.out.println(squeue);
return result;
}
二叉搜索树的后序遍历序列
思路:二叉搜索树,左子树节点的值都小于根节点root
,右子树节点的值都大于跟节点root
。\
// 递归判断左右子树就可以了
public boolean VerifySquenceOfBST(int [] sequence) {
return VerifySquenceOfBST(sequence,0,sequence.length-1);
}
public boolean VerifySquenceOfBST(int [] sequence,int begin,int end) {
if(sequence==null || sequence.length <=0){return false;}
int root = sequence[end];
int i=begin;
for(;i<end;i++){
if(sequence[i]>root) break;
}
// 确定右子树中的值都大于根节点
for(int j=i;j<end;j++){
if(sequence[j]<root) return false;
}
boolean left = true, right = true;
if(i>0 && i!= begin){
left = VerifySquenceOfBST(sequence,begin,i-1);
}
if(i<end){
right=VerifySquenceOfBST(sequence,i, end-1);
}
return left&&right;
}
二叉树中和为某一值的路径
路径是从根节点到叶子节点经过的全部节点
思路:
以前序遍历的方式,从根节点出发,递归遍历左子树和右子树。
注意: paths
添加 path
时,要 new ArrayList
来保存当前path
的值,不然所有操作还是在之前的 path
上的。
import java.util.ArrayList;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ArrayList<ArrayList<Integer>> paths = new ArrayList<ArrayList<Integer>>();
public ArrayList<Integer> path = new ArrayList<Integer>();
public int curValue = 0;
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(root==null || target==0){
return paths;
}
path.add(root.val);
curValue += root.val;
boolean isleaf = root.left==null && root.right==null;
if(isleaf && curValue==target){
paths.add(new ArrayList<Integer>(path));
}
if(root.left!=null){
FindPath( root.left,target);
}
if(root.right!=null){
FindPath( root.right,target);
}
path.remove(path.size()-1);
curValue -= root.val;
return paths;
}
}
>>>>>>> 回到目录
二叉搜索树与双向链表
思路:
按照(递归)中序遍历的顺序,让左子树节点指向父节点(即,左子树的 right
指向父节点),让右子树节点指向父节点(即,右子树的left
指向父节点)。
每个节点都指向前一个节点(left,父节点),每个左侧的节点都指向后一个节点(right,父节点)
注意:空节点的情况。
TreeNode lastNode = null;
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree==null)
return null;
if(pRootOfTree.left==null&&pRootOfTree.right==null)
return pRootOfTree;
ConvertTree(pRootOfTree);
TreeNode headNode = lastNode;
while(headNode.left != null){
headNode = headNode.left;
}
return headNode;
}
public void ConvertTree(TreeNode root) {
TreeNode current = root;
if(current.left != null){
ConvertTree(current.left);
}
current.left = lastNode;
if(lastNode != null){
lastNode.right = current;
}
lastNode = current;
if(current.right != null){
ConvertTree(current.right);
}
}
链接:https://www.nowcoder.com/questionTerminal/947f6eb80d944a84850b0538bf0ec3a5
来源:牛客网
> 别人的一个简洁的答案
lastLeft 直接保存了最左边的一个节点
public class Solution { //类似树的线索化,相当简洁
TreeNode pre=null;
TreeNode lastLeft=null;
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree==null){
return null;
}
Convert(pRootOfTree.left);
pRootOfTree.left=pre;
if(pre!=null)pre.right=pRootOfTree;
pre=pRootOfTree;
lastLeft=lastLeft==null?pRootOfTree:lastLeft;
Convert(pRootOfTree.right);
return lastLeft;
}
}
字符串的排列
思路:
递归法
问题转换为先固定第一个字符,求剩余字符的排列;求剩余字符排列时跟原问题一样。
(1) 遍历出所有可能出现在第一个位置的字符(即:依次将第一个字符同后面所有字符交换);
(2) 固定第一个字符,求后面字符的排列(即:在第1步的遍历过程中,插入递归进行实现)。
需要注意的几点:
(1) 先确定递归结束的条件,例如本题中可设begin == str.size() - 1;
(2) 形如 aba 或 aa 等特殊测试用例的情况,vector在进行push_back时是不考虑重复情况的,需要自行控制;
(3) 输出的排列可能不是按字典顺序排列的,可能导致无法完全通过测试用例,考虑输出前排序,或者递归之后取消复位操作。
[外链图片转存失败(img-DQKVqDKp-1562320247848)(https://github.com/kathy775/hellokiki/blob/master/img/note/nowcoder/jianOffer38.png?raw=true)]
注意:
TreeSet
set转ArrayList
import java.util.*;
public class Solution {
public Set<String> resultSet = new TreeSet<>();
public int begin = 0;
public ArrayList<String> Permutation(String str) {
if(str==null || str.length()==0){
return new ArrayList<String>();
}
char tmp;
char[] chArr = str.toCharArray();
for(int i=begin;i<str.length();i++){
swap(chArr,begin,i);
begin ++;
str = new String(chArr);
resultSet.add(str);
Permutation(str);
begin--;
swap(chArr,begin,i);
}
return new ArrayList<String>(resultSet);
}
public void swap(char[] chArr,int i,int j){
char tmp = chArr[j];
chArr[j] = chArr[i];
chArr[i] = tmp;
}
}
>>>>>>> 回到目录
数组中出现次数超过一半的数字
思路:
在有序状态下,出现次数超过一半的数字一定在中位数的位置上。
方法一 快排(递归)
利用快排的方法,得到位于 mid 的值。
方法二 统计次数
2.1 见书P208
满足题意的数字,它的出现次数大于其他所有数字出现次数的和。
2.2 直接 HashMap 统计出每个数字的出现次数。只要判断 HashMap 中是否存在一个 key 对应的 value 大于长度一半。
注意:
除2运算可以用n>>1
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
int num = partition(array,0,array.length-1);
return isThanHalf(array, num);
}
// 快排,返回位于数组中 mid 的数
public int partition(int [] array,int start,int end){
int pivote = array[start];
int mid = array.length >> 1;
int startTmp = start, endTmp = end;
while(start < end){
while(start < end && array[end]>=pivote){
end--;
}
array[start] = array[end];
while(start < end && array[start]<=pivote){
start++;
}
array[end] = array[start];
}
array[start] = pivote;
if(start == mid){
return array[mid];
}else if(start<mid){
return partition(array,start+1,endTmp);
}else {
return partition(array,startTmp,start-1);
}
}
// 判断位于 mid 的数的出现次数是否超过一半
public int isThanHalf(int [] array,int num){
int count = 0;
for(int i : array){
if(i == num) count++;
}
if(count > (array.length>>1)) return num;
else return 0;
}
}
最小的K个数
还是用快排的思想
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> result = new ArrayList<Integer>();
if(k>input.length || input.length==0) return result;
int[] a = partition(input,0,input.length-1,k);
for(int i=0;i<k;i++){
result.add(a[i]);
}
return result;
}
public int[] partition(int [] array,int start,int end,int k){
if(k>array.length-1) k=array.length-1;
int pivote = array[start];
int startTmp = start, endTmp = end;
while(start < end){
while(start < end && array[end]>=pivote){
end--;
}
array[start] = array[end];
while(start < end && array[start]<=pivote){
start++;
}
array[end] = array[start];
}
array[start] = pivote;
if(start == k){
return array;
}else if(start<k){
return partition(array,start+1,endTmp,k);
}else {
return partition(array,startTmp,start-1,k);
}
}
}
还是用快排也可以
每个数加上前面的数(>本身,则替代;反之,保留)
输出最大值即可
整数中1出现的次数
思路:
https://www.nowcoder.com/questionTerminal/bd7f978302044eee894445e244c7eee6
设N = abcde ,其中abcde分别为十进制中各位上的数字。
如果要计算百位上1出现的次数,它要受到3方面的影响:百位上的数字,百位以下(低位)的数字,百位以上(高位)的数字。\
① 如果百位上数字为0,百位上可能出现1的次数由更高位决定。比如:12013,则可以知道百位出现1的情况可能是:100199,11001199,21002199,,…,1110011199,一共1200个。可以看出是由更高位数字(12)决定,并且等于更高位数字(12)乘以 当前位数(100)。
② 如果百位上数字为1,百位上可能出现1的次数不仅受更高位影响还受低位影响。比如:12113,则可以知道百位受高位影响出现的情况是:100199,11001199,21002199,,…,1110011199,一共1200个。和上面情况一样,并且等于更高位数字(12)乘以 当前位数(100)。但同时它还受低位影响,百位出现1的情况是:12100~12113,一共114个,等于低位数字(113)+1。
③ 如果百位上数字大于1(29),则百位上出现1的情况仅由更高位决定,比如12213,则百位出现1的情况是:100199,11001199,21002199,…,1110011199,1210012199,一共有1300个,并且等于更高位数字+1(12+1)乘以当前位数(100)。
思路理解
主要通过当前位来看,位有:个位、十位、百位…
比如,123,则个位是3,十位是2,百位是1
在个位的时候,个位可能出现1的情况,要看十位和百位。
比如有:1、(11,21…91)、101、111、121
(12 + 1)*1
在十位的时候,十位可能出现1的情况。
比如110
当前位是1时,比如十位是1
low+1,是指10 … 1? (这个大小等于low),+1 是指10的时候十位的1
high*i ,110 … 119 这段数字十位上是1
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
int low=0,high=0,cur=0,i=1,count=0;
while(n/i != 0){
low = n-(n/i)*i;
high = n/(i*10);
cur = n/i%10;
if(cur == 0){
count += high*i;
}else if(cur == 1){
count += low+1+high*i;
}else{
count += (high+1)*i;
}
i = i*10;
}
return count;
}
}
把数组排成最小的数
思路:
对数组进行排序(自定义一个比较大小的函数,比较两个字符串s1, s2大小的时候,先将它们拼接起来,比较s1+s2,和s2+s1那个大,如果s1+s2大,那说明s2应该放前面,所以按这个规则,s2就应该排在s1前面)
把排序后的数组按顺序拼接起来,就是答案。
public String PrintMinNumber(int [] numbers) {
for(int i=0;i<numbers.length;i++){
for(int j=i+1;j<numbers.length;j++){
int num1 = Integer.parseInt(numbers[i]+""+numbers[j]);
int num2 = Integer.parseInt(numbers[j]+""+numbers[i]);
if(num1 > num2){
int tmp = numbers[i];
numbers[i] = numbers[j];
numbers[j] = tmp;
}
}
}
String result = "";
for(int i : numbers){
result += i;
}
return result;
}
丑数
参考别人的代码,这个思路比较清晰,看上去直观一点。
1 2 3 4 5 6 [7] 8 9 10 [11] 12
public static int GetUglyNumber_Solution(int index) {
if(index < 7)return index;
int[] res = new int[index];
res[0]=1;
int t2=0,t3=0,t5=0;
for(int i=1;i<index;i++){
res[i] = Math.min(res[t2]*2, Math.min(res[t3]*3, res[t5]*5));
if(res[i] == res[t2]*2) t2++;
if(res[i] == res[t3]*3) t3++;
if(res[i] == res[t5]*5) t5++;
}
return res[index-1];
}
public class Solution {
public int FirstNotRepeatingChar(String str) {
for(int i=0;i<str.length();i++){
if(str.indexOf(str.charAt(i)) ==str.lastIndexOf(str.charAt(i)))
return i;
}
return -1;
}
}
public class Solution {
public int InversePairs(int [] array) {
if(array.length <=0) return 0;
int[] copy = new int[array.length];
for(int i=0;i<array.length;i++) copy[i] = array[i];
int count = mergeCount(array,copy,0,array.length-1);
return count%1000000007;
}
public int mergeCount(int [] array,int [] copy,int start,int end) {
if(start==end){
copy[start]=array[start];
return 0;
}
int mid = (end-start)/2;
int left = mergeCount(copy,array,start,start+mid);
int right = mergeCount(copy,array,start+mid+1,end);
int i = start+mid;
int j = end,index = end;
int count = 0;
while(i>=start && j>=start+mid+1){
if(array[i] > array[j]){
copy[index--]= array[i--];
count += j-start-mid;
}else copy[index--]= array[j--];
}
for(;i>=start;i--)copy[index--]= array[i];
for(;j>=start+mid+1;j--)copy[index--]= array[j];
return count+left+right;
}
}
两个链表的第一个公共结点
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while(p1 != p2){
p1 = p1==null?pHead2:p1.next;
p2 = p2==null?pHead1:p2.next;
}
return p1;
}
思路:
(方法1)因为data中都是整数,所以可以稍微变一下,不是搜索k的两个位置,而是搜索k-0.5和k+0.5
这两个数应该插入的位置,然后相减即可。
(参考别人的)
(方法2)用二分法找到数字k
第一次出现的位置和最后一次出现的位置,相减。
public int GetNumberOfK(int [] array , int k) {
return binarySearch(array,k+0.5)-binarySearch(array,k-0.5);
}
public int binarySearch(int [] array , double k) {
int start = 0,end = array.length-1;
int mid = 0;
while(start<=end){ // 注意这里的条件
mid = (end-start)/2+start;
if(array[mid]>k) end = mid-1;
else if(array[mid]<k) start = mid+1;
}
return start;
}
0~n-1 中缺失的数字
思路:每个未缺失的数字,它的下标和值本身相等。通过二分法找到下标和值不相等的数字。
思路:
方法一:递归,直接比较左右子树的高度
方法二:层次遍历得到高度
public int TreeDepth(TreeNode root) {
return root==null?0:(Math.max(TreeDepth(root.left),TreeDepth(root.right))+1);
}
参考的思路:
如果改为从下往上遍历,如果子树是平衡二叉树,则返回子树的高度;如果发现子树不是平衡二叉树,则直接停止遍历,这样至多只对每个结点访问一次。
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
return getHeight(root)!=-1;
}
public int getHeight(TreeNode root) {
if(root==null)return 0;
int left = getHeight(root.left);
if(left == -1) return -1;
int right = getHeight(root.right);
if(right == -1) return -1;
return Math.abs(left-right)>1?-1:Math.max(left,right)+1;
}
}
和为S的两个数字
思路:
用两个指针start
,end
如果start
位置的值和end
位置的值的和>sum
,则end--
;
否则,start++
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
ArrayList<Integer> result = new ArrayList<>();
if(array==null || array.length==0||sum==0)return result;
int start = 0,end = array.length-1;
while(end >= start){
int tmp=array[start]+array[end];
if(tmp == sum){
result.add(array[start]);result.add(array[end]);
break;
}else if(tmp<sum)start++;
else end --;
}
return result;
}
和为S的连续正数序列
import java.util.ArrayList;
public class Solution {
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer> > result = new ArrayList<>();
int start=1,end = 2,num=3;
while(start < end){
if(num < sum){
end++;num+=end;
}else if(num > sum){
num-=start;start++;
}else{
ArrayList<Integer> nums = new ArrayList<Integer>();
for(int i=start;i<=end;i++){
nums.add(i);
}
result.add(nums);
num-=start;start++;end++;num+=end;
}
}
return result;
}
}
翻转单词顺序列
方法一:
O(n), n是单词个数
public class Solution {
public String ReverseSentence(String str) {
if(str.replace(" ","").length()==0)return str;
String[] sarr = str.split(" ");
String result = "";
for(int i=sarr.length-1;i>=0;i--){
result+=sarr[i]+" ";
// if(i!=0) result+=" ";
}
return result.substring(0,result.length()-1);
}
}
方法二:
用start
和end
指针,将sarr
进行交换。
再对sarr
输出的字符串进行一些处理,再输出。
左旋转字符串
方法一:
public class Solution {
public String LeftRotateString(String str,int n) {
if(n<0 || n>str.length()) return str;
String s1 = str.substring(0,n);
String s2 = str.substring(n);
return s2+s1;
}
}
方法二:
原理:$YX = (X^TY^T)^T$
参考:https://www.nowcoder.com/questionTerminal/12d959b108cb42b1ab72cef4d36af5ec
string LeftRotateString(string str, int n)
{
int len = str.size();
if(len == 0) return str;
n %= len;
for(int i = 0, j = n - 1; i < j; ++i, --j) swap(str[i], str[j]);
for(int i = n, j = len - 1; i < j; ++i, --j) swap(str[i], str[j]);
for(int i = 0, j = len - 1; i < j; ++i, --j) swap(str[i], str[j]);
return str;
}
扑克牌顺子
思路:
zeroNum
gapNum
,也就是相邻数字之间的差。zeroNum
小于gapNum
,则输出false
import java.util.Arrays;
public class Solution {
public boolean isContinuous(int [] numbers) {
if(numbers.length==0) return false;
Arrays.sort(numbers);
int zeroNum = 0,gapNum = 0;
for(int i=0;i<numbers.length && numbers[i]==0;i++) zeroNum++;
int start=zeroNum,end=start+1;
// 为什么是 start=zeroNum?
// 因为数组前面的0不用统计gap
while(end<numbers.length){
if(numbers[start]==numbers[end]) return false;
gapNum += numbers[end]-numbers[start]-1;
start=end;end++;
}
return gapNum>zeroNum?false:true;
}
}
圆圈中最后剩下的数
public class Solution {
public int LastRemaining_Solution(int n, int m) {
if(n<1||m<1)return -1;
int last=0;
for(int i=2;i<=n;i++)
last = (last+m)%i;
return last;
}
}