1.剑指 Offer 03. 数组中重复的数字
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
(1)自己写的,使用map统计词频,大于1的就是重复的
Map<Integer,Integer> map=new HashMap<>();
for(int i=0;i<nums.length;i++){
if(map.containsKey(nums[i])){
int count=map.get(nums[i]);
count++;
map.put(nums[i],count);
}else{
map.put(nums[i],1);
}
}
for(Integer i:map.keySet()){
if(map.get(i)>1){
return i;
}
}
return -1;
(2)使用哈希表(Set)记录数组的各个数字,当查找到重复数字则直接返回。
(3)原地交换
在一个长度为 n 的数组 nums 里的所有数字都在 0 ~ n-1 的范围内 。 此说明含义:数组元素的 索引 和 值 是 一对多 的关系。
因此,可遍历数组并通过交换操作,使元素的 索引 与 值 一一对应(即 nums[i] = i)。因而,就能通过索引映射对应的值,起到与字典等价的作用。
遍历中,第一次遇到数字 xx 时,将其交换至索引 xx 处;而当第二次遇到数字 xx 时,一定有 nums[x] = xnums[x]=x ,此时即可得到一组重复数字。
public int findRepeatNumber(int[] nums) {
for(int i=0;i<nums.length;i++){
if(i!=nums[i]){
if(nums[i]==nums[nums[i]]){
return nums[i];
}
int temp=nums[i];
nums[i]=nums[temp];
nums[temp]=temp;
}
}
return -1;
}
2.定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
(1)利用了小根堆
class MinStack {
/** initialize your data structure here. */
Stack<Integer> stack;
PriorityQueue<Integer> priorityQueue;
public MinStack() {
this.stack=new Stack<>();
this.priorityQueue=new PriorityQueue<Integer>();
}
// public static class MyComparator implements Comparator<Integer>{//自定义比较器有测试用例不通过
// @Override
// public int compare(Integer o1,Integer o2){
// return o1-o2;
// }
// }
public void push(int x) {
this.stack.push(x);
this.priorityQueue.add(x);
}
public void pop() {
this.priorityQueue.remove(this.stack.pop());
}
public int top() {
return this.stack.peek();
}
public int min() {
return priorityQueue.peek();
}
}
(2)单调栈1:与数据栈不同步pop,min栈没有重复元素
class MinStack {
Stack<Integer> data;
Stack<Integer> min;
/** initialize your data structure here. */
public MinStack() {
this.data=new Stack<>();
this.min=new Stack<>();
}
public void push(int x) {
this.data.push(x);
if(this.min.isEmpty()||x<=this.min.peek()){
this.min.push(x);
}
}
public void pop() {
int a=this.data.pop();
if(a==min.peek()){
this.min.pop();
}
}
public int top() {
return this.data.peek();
}
public int min() {
return this.min.peek();
}
}
(3)单调栈2:与数据栈同步pop,min栈会有重复元素
class MinStack {
Stack<Integer> data;
Stack<Integer> min;
/** initialize your data structure here. */
public MinStack() {
this.data=new Stack<>();
this.min=new Stack<>();
}
public void push(int x) {
if(this.data.isEmpty()){
this.min.push(x);
}else{
int a=this.min.peek();
if(x<=a){
this.min.push(x);
}else{
this.min.push(a);
}
}
this.data.push(x);
}
public void pop() {
this.data.pop();
this.min.pop();
}
public int top() {
return this.data.peek();
}
public int min() {
return this.min.peek();
}
}
3.从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/
9 20
/
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res=new ArrayList<>();
Queue<TreeNode> queue=new LinkedList<>();
if(root!=null){
queue.add(root);
}
while(!queue.isEmpty()){
List<Integer> list=new ArrayList<>();
for(int i=queue.size();i>0;i--){ //将一层的全部出队列
TreeNode zhong=queue.remove();
list.add(zhong.val);
if(zhong.left!=null){
queue.add(zhong.left);
}
if(zhong.right!=null){
queue.add(zhong.right);
}
}
res.add(list);
}
return res;
}
4.数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
(1)map统计
public int majorityElement(int[] nums) {
Map<Integer,Integer> map=new HashMap();
for(int i=0;i<nums.length;i++){
if(!map.containsKey(nums[i])){
map.put(nums[i],1);
}else{
int num=map.get(nums[i]);
num++;
map.put(nums[i],num);
}
}
int bound=nums.length/2;
for(Integer key:map.keySet()){
if(map.get(key)>bound){
return key;
}
}
return -1;
}
(2)摩尔投票法:不同的两两抵消
public int majorityElement(int[] nums) {
int x=0;
int vote=0;
for(int n:nums){
if(vote==0){
x=n;
}
if(n==x){
vote++;
}else{
vote--;
}
}
return x;
}
5.统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
(1)从前往后统计
if(nums==null||nums.length==0){
return 0;
}
int count=0;
for(int i=0;i<nums.length;i++){
if(nums[i]==target){
count++;
}
}
return count;
(2)二分法:求target的右边界和target-1的右边界
public int search(int[] nums, int target) {
if(nums==null||nums.length==0){
return 0;
}
if(nums.length==1&&nums[0]==target){//防止[1] 1的情况
return 1;
}
return helper(nums,target)-helper(nums,target-1);
}
public int helper(int[] nums,int target){
int l=0;
int r=nums.length-1;
while(l<=r){//注意=号
int mid=l+(r-l)/2;
if(nums[mid]<=target){//因为最后要返回l,所以是<=
l=mid+1;
}else{
r=mid-1;
}
}
return l;
}
(1)遍历
public int missingNumber(int[] nums) {
int i=0;
for(;i<nums.length;i++){
if(i!=nums[i]){
return i;
}
}
return i;
}
(1)二分:根据题意,数组可以按照以下规则划分为两部分。
左子数组:nums[i]=i ;
右子数组:nums[i] !=i
缺失的数字等于 “右子数组的首位元素” 对应的索引;因此考虑使用二分法查找 “右子数组的首位元素”
public int missingNumber(int[] nums) {
int l=0;
int r=nums.length-1;
while(l<=r){
int mid=l+(r-l)/2;
if(nums[mid]==mid){
l=mid+1;
}else{
r=mid-1;
}
}
return l;
}
7.给定一棵二叉搜索树,请找出其中第k大的节点。
示例 1:
输入: root = [3,1,4,null,2], k = 1
3
/
1 4
2
输出: 4
示例 2:
输入: root = [5,3,6,2,4,null,null,1], k = 3
5
/
3 6
/
2 4
/
1
输出: 4
(1)遍历并排序二叉树
public int kthLargest(TreeNode root, int k) {
List<Integer> list=new ArrayList<>();
pre(root,list);
Collections.sort(list);
Collections.reverse(list);
return list.get(k-1);
}
public void pre(TreeNode root,List<Integer> list){
if(root==null){
return ;
}
list.add(root.val);
pre(root.left,list);
pre(root.right,list);
}
(2)搜索二叉树中序遍历的倒序即为递减序列
class Solution {
int k;
int res;
public int kthLargest(TreeNode root, int k) {
this.k=k;
dfs(root);
return res;
}
public void dfs(TreeNode root){
if(root==null){
return ;
}
dfs(root.right); //注意倒序是先root.right(先去右子树)
this.k--;
if(k==0){
res=root.val;
return ;
}
dfs(root.left);
}
}
8.剑指 Offer 57 - II. 和为s的连续正数序列
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
示例 1:
输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:
输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
(1)暴力解法:枚举每个正整数为起点,判断以它为起点的序列和sum 是否等于target 即可,由于题目要求序列长度至少大于 2,所以枚举的上界为l<=(target-1)/2
public int[][] findContinuousSequence(int target) {
List<int []> res=new ArrayList<int []>();
int sum=0;
for(int l=1;l<=(target-1)/2;l++){
for(int r=l;;r++){
sum+=r;
if(sum>target){
sum=0;
break;
}else if(sum==target){
int[] temp=new int[r-l+1];
for(int tem=l;tem<=r;tem++){
temp[tem-l]=tem;
}
res.add(temp);
sum=0;
break;
}
}
}
return res.toArray(new int[res.size()][]);
}
(2)滑动指针:我们用两个指针 l和 r表示当前枚举到的以 l为起点到 r 的区间,sum 表示 [l,r]的区间和
public int[][] findContinuousSequence(int target) {
List<int []> res=new ArrayList<int []>();
int l=1;
int r=1;
int sum=0;
while(l<=(target-1)/2)
{
if(sum>target){
sum-=l;
l++;
}else if(sum<target){
sum+=r;
r++;
}else if(sum==target){
int[] temp=new int[r-l];
for(int tem=l;tem<r;tem++){
temp[tem-l]=tem;
}
res.add(temp);
sum-=l;
l++;
}
}
return res.toArray(new int[res.size()][]);
}
9.输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。
示例 1:
输入: “the sky is blue”
输出: “blue is sky the”
示例 2:
输入: " hello world! "
输出: “world! hello”
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
(1)分割 + 倒序:利用 “字符串分割”、“列表倒序” 的内置函数 (面试时不建议使用) ,可简便地实现本题的字符串翻转要求。
public String reverseWords(String s) {
String [] strs=s.trim().split(" ");
StringBuilder res=new StringBuilder();
for(int i=strs.length-1;i>-1;i--){
if(strs[i].equals("")){//注意是"",不是" "
continue;
}
res.append(strs[i]);
res.append(" ");
}
return res.toString().trim();
}
(2)方法一:双指针
倒序遍历字符串 ss ,记录单词左右索引边界 i , j ;
每确定一个单词的边界,则将其添加至单词列表 res ;
最终,将单词列表拼接为字符串,并返回即可。
public String reverseWords(String s) {
s=s.trim();
int i=s.length()-1;
int j=i;
StringBuilder res=new StringBuilder();
while(i>=0){
while(i>=0&&s.charAt(i)!=' ') i--;
res.append(s.substring(i+1,j+1)+" "); //注意是substring不是subString
while(i>=0&&s.charAt(i)==' ') i--;
j=i;
}
return res.toString().trim();
}
10,从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
示例 1:
输入: [1,2,3,4,5]
输出: True
示例 2:
输入: [0,0,1,2,5]
输出: True
此 55 张牌是顺子的 充分条件 如下:
除大小王外,所有牌 无重复 ;
设此 55 张牌中最大的牌为 max ,最小的牌为 min (大小王除外),则需满足:
max - min < 5
(1)集合 Set + 遍历
遍历五张牌,遇到大小王(即 0 )直接跳过。
判别重复: 利用 Set 实现遍历判重, Set 的查找方法的时间复杂度为 O(1) ;
获取最大 / 最小的牌: 借助辅助变量 ma 和 mi ,遍历统计即可。
public boolean isStraight(int[] nums) {
Set<Integer> set=new HashSet<>();
int max=0;
int min=14;
for(int i=0;i<=nums.length-1;i++){
if(nums[i]==0){
continue;
}
max=Math.max(max,nums[i]);
min=Math.min(min,nums[i]);
if(set.contains(nums[i])){
return false;
}
set.add(nums[i]);
}
return (max-min)<5;
}
(2)排序 + 遍历
先对数组执行排序。
判别重复: 排序数组中的相同元素位置相邻,因此可通过遍历数组,判断 nums[i] = nums[i + 1]是否成立来判重。
获取最大 / 最小的牌: 排序后,数组末位元素 nums[4]为最大牌;元素 nums[joker]为最小牌,其中 joker 为大小王的数量。
public boolean isStraight(int[] nums) {
int zero=0;
Arrays.sort(nums);
for(int i=0;i<nums.length-1;i++){
if(nums[i]==0){
zero++;
}else if(nums[i+1]<=nums[i]){
return false;
}
}
return (nums[4]-nums[zero])<5;
}
11.0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
示例 1:
输入: n = 5, m = 3
输出: 3
示例 2:
输入: n = 10, m = 17
输出: 2
(1)使用list集合去存储
public int lastRemaining(int n, int m) {
List<Integer> list=new ArrayList<>();
for(int i=0;i<n;i++){
list.add(i);
}
int idx=0;
while(n>1){
idx=(idx+m-1)%n;
list.remove(idx);
n--;
}
return list.get(0);
}
(2)数学方法
public int lastRemaining(int n, int m) {
int res=0;
for(int i=2;i<=n;i++){
res=(res+m)%i;
}
return res;
}
具体参考:https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/javajie-jue-yue-se-fu-huan-wen-ti-gao-su-ni-wei-sh/
12.写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
示例:
输入: a = 1, b = 1
输出: 2
public int add(int a, int b) {
int sum=a;
while(b!=0){
sum=a^b;
b=(a&b)<<1;
a=sum;
}
return sum;
}
13.二叉搜索树的最近公共祖先
(1)迭代
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(root!=null){
if(root.val<p.val&&root.val<q.val){
root=root.right;
}else if(root.val>p.val&&root.val>q.val){
root=root.left;
}else{
break;
}
}
return root;
}
(2)递归
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root.val<p.val&&root.val<q.val){
return lowestCommonAncestor(root.right,p,q);
}else if(root.val>p.val&&root.val>q.val){
return lowestCommonAncestor(root.left,p,q);
}
return root;
}
https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/solution/mian-shi-ti-68-i-er-cha-sou-suo-shu-de-zui-jin-g-7/
14.(相似左神中级5:4)写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
public int strToInt(String str) {
str=str.trim();//去掉两边的空格
if(str==null||str.length()==0){
return 0;
}
char[] chars=str.toCharArray();
boolean neg=chars[0]=='-'?true:false;//先判断是否是负数
int binary=Integer.MIN_VALUE/10;//-214748364 边界条件,因为要是小于这个数,*10之后必将超过整数能表示的最小值
int end=Integer.MIN_VALUE%10; //-7 边界条件,当res==-214748364时,cur当前字符要是<-7 那么合并之后也会越界
int res=0; //存放最后的结果
int cur=0; //当前的字符
int j=0; //字符数组开始的位置
if(neg||chars[0]=='+'){
j=1; //若是负数或者第一个符号为'+'时,开始位置为1
}
for(int i=j;i<chars.length;i++){
if(chars[i]<'0'||chars[i]>'9'){//若当前字符不为数字则停止
break;
}
cur='0'-chars[i];
if(res<binary||(res==binary&&cur<end)){ //越界条件
return neg?Integer.MIN_VALUE:Integer.MAX_VALUE;
}
res=res*10+cur;
}
if(!neg&&(res==Integer.MIN_VALUE)){ //防止输入的字符为"2147483648",负数可以表示,但是正数是越界的
return Integer.MAX_VALUE;
}
return neg?res:-res;
}
https://leetcode-cn.com/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/solution/mian-shi-ti-67-ba-zi-fu-chuan-zhuan-huan-cheng-z-4/
15.(相似左神中级6:2)输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。(二叉树的递归套路)
class Solution {
public static class Info{//定义信息的类
public Node start;
public Node end;
public Info(Node start,Node end){
this.start=start;
this.end=end;
}
}
public Node treeToDoublyList(Node root) {
if(root==null){
return null;
}
Info info = dfs(root);//递归的建立双向链表
info.end.right=info.start;//最终需要把链表的头和尾连到一起
info.start.left=info.end;
return info.start;
}
public static Info dfs(Node head){
if(head==null){
return new Info(null,null);
}
Info leftInfo=dfs(head.left);//左子树连成双向链表的头尾信息
Info rightInfo=dfs(head.right);//右子树连成双向链表的头尾信息
if(leftInfo.end!=null){
leftInfo.end.right=head;
}
if(rightInfo.start!=null){
rightInfo.start.left=head;
}
head.left=leftInfo.end;
head.right=rightInfo.start;
return new Info(leftInfo.start!=null ? leftInfo.start : head,rightInfo.end!=null ? rightInfo.end : head);
}
}
16.(相似左神中级6-6)剑指offer42.连续子数组最大和
public int maxSubArray(int[] nums) {
if(nums==null||nums.length==0){
return 0;
}
int sum=Integer.MIN_VALUE;
int cur=0;
for(int i=0;i<nums.length;i++){
cur+=nums[i];
if(cur>sum){
sum=cur;
}
if(cur<0){
cur=0;
}
}
return sum;
}