目录
剑指Offer51~60
丑数
题链
题解:根据题目我们可以设三个数字分别为2x,3y,5z,然后按照从小到大的顺序更新这三个数字,每次选择三个数字中最小的数字,并让对应的变量加1,假设更新的是2x,那么x+1,其他的同理
这道题的思想其实可以类比合并两个有序数组,只不过这里是三个部分,和有序数组不相同的是这里要保证三个数组中去除掉相等的数字.
public int nthUglyNumber(int n) {
int[] f = new int[n+1];
f[1] = 1;
int p2 = 1,p3 = 1,p5 = 1;
for(int i = 2; i <= n; i++){
int num1 = p2*2;
int num2 = p3*3;
int num3 = p5*5;
f[i] = Math.min(Math.min(num1,num2),num3);
if(f[i] == num1){
p2++;
}
if(f[i] == num2){
p3++;
}
if(f[i] == num3){
p5++;
}
}
return f[n];
}
第一个只出现一次的字符
题链
题解:根据题意我们记录一个哈希表HashMap记录每个字母出现的次数.然后从前往后遍历数组,找到第一个出现次数为1的字母.
public char firstUniqChar(String s) {
if(s.length() == 0){
return ' ';
}
int len = s.length();
//因为只有小写字母,所以也可以用数组统计出现次数
int[] cnt = new int[26];
for(int i = 0; i < len; i++){
cnt[(s.charAt(i) - 'a')]++;
}
for(int i = 0; i < len; i++){
if(cnt[s.charAt(i) - 'a'] == 1){
return s.charAt(i);
}
}
return ' ';
}
数组中的逆序对
题链
题解:利用归并排序的思想,在合并两个有序数组的时候计算两个有序数组中的逆序对个数.然后将所有区间中的逆序对个数求和.
假设两个有序子数组:[x1,x2,x3],[y1,y2,y3],当x1<=y1时,x++指向x2;当x1>y1时,说明[x1,y1]是一对逆序对,而x3>=x2>=x1,所以此时的逆序对个数为3,y++.按照这个规则遍历完两个数组.
int cnt = 0;
public int reversePairs(int[] nums) {
int len = nums.length;
int[] temp = new int[len];
int[] curNums = Arrays.copyOf(nums,len);
reversePairs(curNums,0,len-1,temp);
return cnt;
}
private void reversePairs(int[] nums,int left,int right,int[] temp){
if(left < right){
int mid = left + right >> 1;
reversePairs(nums,left,mid,temp);
reversePairs(nums,mid+1,right,temp);
merge(nums,left,mid,right,temp);
}
}
private void merge(int[] nums,int left,int mid,int right,int[] temp){
for(int i = left; i <= right;i++){
temp[i] = nums[i];
}
int l1 = left,l2 = mid+1;
int l = left;
while(l1 <= mid && l2 <= right){
if(temp[l1] > temp[l2]){
nums[l] = temp[l2];
cnt+= mid-l1+1;
l2++;
l++;
}else {
nums[l] = temp[l1];
l1++;
l++;
}
}
if(l1 > mid){
for(;l2 <= right;l2++){
nums[l++] = temp[l2];
}
}else if(l2 > right){
for(;l1<=mid;l1++){
nums[l++] = temp[l1];
}
}
}
两个链表的第一个公共节点
题链
题解:经典链表题,首先计算两个链表的长度l1,l2,然后让链表较长的先行走(abs(l1-l2))步,之后每次让两个链表往前走一步,看对应的节点是否相同,如果相同,则返回该节点.
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int lenA = getLen(headA);
int lenB = getLen(headB);
ListNode curA = headA;
ListNode curB = headB;
if(lenA < lenB){
int diff = lenB - lenA;
while(diff != 0){
curB = curB.next;
diff--;
}
}else{
int diff = lenA - lenB;
while(diff != 0){
curA = curA.next;
diff--;
}
}
while(curA != curB){
curA = curA.next;
curB = curB.next;
}
return curA;
}
public int getLen(ListNode head){
ListNode cur = head;
int len = 0;
while(cur != null){
len++;
cur = cur.next;
}
return len;
}
在排序数组中查找数字I
题链
题解:首先顺序遍历的方式是可以做的,但这种方式在乱序数组中也可以做.所以在排序数组中,应该考虑更优化的方式.既然有序,那么应该考虑二分.本题即是找到数组中第一次出现target的下标和最后一次找到target的下标.
public int search(int[] nums, int target) {
int ll = 0,rr = 0;
if(nums.length == 0){
return 0;
}
int l = 0,r = nums.length-1;
while(l < r){
int mid = l + r >> 1;
if(nums[mid] >= target){
r = mid;
}else{
l = mid+1;
}
}
ll = l;
l = 0;r = nums.length-1;
while(l < r){
int mid = l + r + 1 >> 1;
if(nums[mid] <= target){
l = mid;
}else{
r = mid-1;
}
}
rr = l;
//因为可能出现整个数组中都没有target的情况,所以要单独拎出来讨论
if(nums[ll] != target || nums[rr] != target){
return 0;
}
return ll > rr ? 0 : rr-ll+1;
}
0~n-1中缺失的数字
题链
题解:位运算.利用异或运算求解.异或运算有两个性质:0和任何数异或都是该数,任意两个相同的数异或是0,根据这两个结果我们可以将1~n和数组中的所有数字异或一遍即可得到最终结果
public int missingNumber(int[] nums) {
int n = nums.length;
int ans = 0;
for(int i = 0; i < n;i++){
ans ^= nums[i];
ans ^= (i+1);
}
return ans;
}
二叉搜索树的第k大节点
题链
题解:中序遍历二叉搜索树:递归左节点,k–,当k为0即返回该节点的值即可,递归右节点.
public int val = 0;
public int k;
public int kthLargest(TreeNode root, int _k) {
k = _k;
dfs(root);
return val;
}
public void dfs(TreeNode root){
if(root == null){
return;
}
dfs(root.right);
k--;
if(k == 0){
val = root.val;
return;
}
dfs(root.left);
}
二叉树的深度
题链
题解:递归遍历二叉树即可,
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
平衡二叉树
题链
题解:[二叉树的深度]题型的变形,在求解二叉树深度的过程中,每次判断左子树和右子树的高度差是否小于等于1,如果不是直接返回-1即可.
public boolean flag = true;
public boolean isBalanced(TreeNode root) {
getLenth(root);
return flag;
}
public int getLenth(TreeNode root){
if(!flag){
return -1;
}
if(root == null){
return 0;
}
int leftL = getLenth(root.left) + 1;
int rightL = getLenth(root.right) + 1;
if(Math.abs(leftL-rightL) > 1){
flag = false;
return -1;
}else{
return Math.max(leftL,rightL);
}
}
数组中数字出现的次数
题链
题解:位运算.利用异或运算求解.如果数组中只有1个数字出现1次那么根据异或运算的性质直接将数组中的所有数字异或完后得到的就是结果,但本题的数组中有2个数字只出现了1次,因此全部异或完后得到的是这2个数字异或后的结果key.假设我们要求的这两个数字为A,B.但其实我们利用key将数组分成两部分,A+一部分出现次数为2的数字,B+一部分出现次数为2的数字.那么我们只需要分别对这两部分全部异或即可得到结果.那么如何利用key分成两部分呢
我们知道异或的含义是当某一位上数字不同时得到的结果为1,那么根据这个性质我们可以先计算出key的某一位为1的位数k,利用这个k将数组中的数字分成两部分,一部分第k位上为0,另一部分第k位上为1,且出现2次的数字会同时在一部分里.
public int[] singleNumbers(int[] nums) {
int len = nums.length;
int ans = 0;
for(int num : nums){
ans ^= num;
}
int k = 0;
while(((ans >> k) & 1) == 0){
k++;
}
int ans1 = 0,ans2 = 0;
for(int num : nums){
if(((num >> k) & 1) == 0){
ans1 ^= num;
}else{
ans2 ^= num;
}
}
return new int[]{ans1,ans2};
}
数组中数字出现的次数II
题链https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/)
题解1:除了1个数字外其他数字的次数都出现了3次.对于所有数字上的第k位进行相加,得到的结果一定是3的倍数或者是3的倍数+1.而具体是哪个其实取决于那个只出现了1次的数字上第k位是0还是1,根据这个思路,我们可以反推得到只出现一次的数字
从低位到高位每次将所有数字的第k位的值相加再模3得到的值如果是1,就左移k位
public int singleNumber(int[] nums) {
int ans = 0;
for(int i = 0; i < 32; i++){
int cur = 0;
for(int num : nums){
cur += ((num >> i) & 1);
cur %= 3;
}
ans += (cur << i);
}
return ans;
}