【剑指offer】数据结构——数

在这里插入图片描述

目录

  • 数据结构——数
    • 直接解
      • 【剑指offer】43. 1~n 整数中 1 出现的次数
      • 【剑指offer】44. 数字序列中某一位的数字
      • 【剑指offer】49. 丑数
      • 【剑指offer】60. n个骰子的点数
      • 【剑指offer】62. 圆圈中最后剩下的数字
      • 【剑指offer】64. 求1+2+…+n
    • 特殊解——分治法 :
      • 【剑指offer】16. 数值的整数次方
    • 特殊解——位运算 :
      • 【剑指offer】15. 二进制中1的个数
      • 【剑指offer】65. 不用加减乘除做加法
    • 特殊解——贪心算法 :
      • 【剑指offer】14.1. 剪绳子
      • 【剑指offer】14.2. 剪绳子 II
    • 特殊解——动态规划 :
      • 【剑指offer】46. 把数字翻译成字符串

数据结构——数

直接解

【剑指offer】43. 1~n 整数中 1 出现的次数

题目描述
在这里插入图片描述

在这里插入图片描述


// 力扣
// 输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

// 例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一
// 共出现了5次。

 
// 牛客

// 求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?
// 为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现
// 6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加
// 普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1
// 出现的次数)。

题解:

// 题目比较难,需要比较数学的处理。我们将个位,十位,百位等等这些位分开讨论。
// 比如n=126,1出现的次数 = (个位的1出现次数)+(十位的1出现次数)+(百位的1出现次数)


// 力扣
// 这里是无注释的干净算法代码,注释版在下面
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:35 MB, 在所有 Java 提交中击败了86.22%的用户
class Solution {
    public int countDigitOne(int n) {
		int count = 0;
		int weight = 0;
		int low = 0;
		int high = n;
		for (int base = 1; base <= n; base *= 10) { 
			weight = high % 10;  
			high = high / 10;
			low = n % base;
			if (weight > 1)
				count += (high * base + base);
			else if (weight == 1)
				count += (high * base + low + 1);
			else 
				count += (high * base);
		}
		return count;
	}
}


// 力扣
// 下面是代码注释
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:35 MB, 在所有 Java 提交中击败了86.22%的用户
class Solution {
    public int countDigitOne(int n) {
		int count = 0;   // 保存答案,记录1出现的次数
		int weight = 0;  // 位指针,从个位开始到十位 百位,直至最高位遍历
		int low = 0;     // 位指针外的低位,初始化为0
		int high = n;    // 位指针外的高位,初始化为n
		// for循环遍历n的位数,weight从最低位个位到n的最高位遍历,
		// 遍历位的基数记为base。
		// 比如n=126,则位指针weight从低到高将遍历6(个位)2(十位)1(百位)
		// 则base分别为1(个位),10(十位),100(百位)。
		for (int base = 1; base <= n; base *= 10) { 
			// 计算位指针weight,高位high÷10取余即可得到,
			// 如n=126, 则weight=6(个位)
			weight = high % 10;  
			// 高位high随着位指针weight的移动而左移,由于high初始化为n
			// 之后算high直接high÷10取整即可。
			// 假如n=126,位指针weight=6,则high = 126 / 10 = 12(取整)
			// 正好是6左边的数,可见位指针weight和高位high的计算是除法的
			// 两种取法。高位high每次÷10时,取整就可以左移得到下一次遍历的高位high,
			// 取余就可以得到下一次遍历的位指针weight。我们利用这个特点
			// 将高位high初始化为n,就可以得到第一个位指针weight。
			high = high / 10;
			// 低位的计算比较巧妙,在循环中,位指针weight从个位开始不断左移
			// 的同时,我们记录了位指针weight所在位的基数base。
			// 假如n=126,位指针weight指向了2,那么所在位的基数base为10(十位)
			// 低位low通过n÷base取余可以得到。换句话说,由于得知位指针weight的
			// 位置和基数,通过除法取余可以得到位指针weight右边的所有内容。
			low = n % base;
			// 计数方法:【"1"出现的次数=(个位的"1"出现次数)+(十位的"1"出现次数)+...】
			// 根据这个原则,指针weight移动到哪位,我们就计算哪位的"1"的出现次数。
			// 当指针遍历的位数字大于1时,该位出现"1"的次数,肯定包含高位数字本身
			// 并且还包含当前位的基数。为什么呢?
			// 假如n=126,指针weight指向6,因为是个位,首先基数base是1。
			// weight>1,个位要到达6,就必须出现过一次"1",所以要+base
			// 如果指针weight指向2,因为是十位,首先base是10,
			// weight>1,十位要达到2,就必须出现过十次"1",所以还是要+base。
			// weight为其他位时也同理。
			
			// 那么加上高位high * base意思也是一样的,n=126,
			// 指针weight指向6,则指针位肯定出现过base次的"1",高位high为12,
			// 能到达n = 12*base + 6*base这么大的数,weight遍历位已经出现过
			// 12 * base轮的"1"了。所以还要加 high * base。
			if (weight > 1)
				count += (high * base + base);
			// 若指针位weight==1,加high * base是不变的,但是由于weight不大于1
			// ,到达当前指针weight这个数时出现的"1"可能不满base次,所以不能直接+base
			// ,因为weight是1,每个和weight搭配的low都会使遍历位出现1次"1",
			// 所以出现了low次"1",再加上low=0还会出现一次"1","1"总共出现了low+1次。
			// 比如weight=1,low=4,那么遍历位的"1"肯定出现了4 + 1次,分别是
			// 10,11,12,13,14。
			else if (weight == 1)
				count += (high * base + low + 1);
			// 如果指针位weight为0,那么当前遍历位和低位都不会对当前遍历位
			// 出现"1"有帮助。当前位出现"1"只可能来源于高位high,直接加 high * base
			else 
				count += (high * base);
		}
		return count;  // 循环结束,返回count
	}
}


// 牛客
// 运行时间:11ms
// 占用内存:9652k
public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
		int count = 0;
        int weight = 0;
        int high = n;
        int low = 0;
        for (int base = 1; base <= n; base *= 10) {
            weight = high % 10;
            high = high / 10;
            low = n % base;
            if (weight > 1)
                count += (high * base) + base;
            else if (weight == 1)
                count += (high * base) + low + 1;
            else 
                count += (high * base);
        }
        return count;
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述



【剑指offer】44. 数字序列中某一位的数字

题目描述

在这里插入图片描述

// 44. 数字序列中某一位的数字


// 力扣
// 数字以0123456789101112131415…的格式序列化到一个字符序列中。
// 在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19
// 位是4,等等。

// 请写一个函数,求任意第n位对应的数字。

题解:

// 1.先求n所在的数字属于几位数,2.后求n所在的数字num,3.最后求n在数字num的第几位
// 如n=11,我们将
// 1:先求n所在的数字属于2位数
// 2:后求n所在的数字为10,
// 3:最后求得n在数字10的第二位,即目标为数字0。

// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:35.3 MB, 在所有 Java 提交中击败了33.22%的用户
class Solution {
    public int findNthDigit(int n) {
		// 位数记录,如11为2位数,123为3位数。初始化digit为1
		int digit = 1;  
		// 当前以位数digit所开始的数字,2位数开始的数字为10,1位数
		// 开始的数字为1(0不算1位数)
		long start = 1;  
		// 在当前位数digit下包含的数字数量。由于位数digit初始化为1,
		// 所以位数为1的数字总共有1 2 3 4 5 6 7 8 9,共9个数字。
		long count = 9;
		// 先求n所在的数字num的位数digit
		// 如果位数数字总数count少于n,执行循环
		while (count < n) {  
			n -= count;   // n减去当前位数数字总数count,若n=15,更新为n=15-9=6
			digit++;      // 位数更新
			start *= 10;  // 位数digit开始的数字start根据位数进行更新
			count = 9 * start * digit;  // count根据start和digit进行更新
		}
		// 若n=15更新为了n=6。位数digit同时可以理解为所在位数区间内
		// 所有数字的长度,digit=2,说明所在位数区间内所有数字长度都为2,
		// 而n-1表示从start(start=10)数字第一位开始,到达n=6位置的数位长度
		// (所经历的位)即1->0->1->1->1->2,长度为5。
		// 那么(n-1)/digit即可得到数字本身从start开始,在位数区间的数字长度(所经历的数)
		// 即10->11->12,长度为2。此时再加上start即可得到n所在的数字num
		long num = start + (n - 1) / digit;
		// 求n所在数字的第几位
		// n-1表示从start(start=10)数字第一位开始,到达n=6位置的数位长度
		// 我们直接看这个长度能不能被digit整除即可
		int i = (n - 1) % digit;
		return Long.toString(num).charAt(i) - '0';
    }
}


【剑指offer】49. 丑数

题目描述

在这里插入图片描述
在这里插入图片描述

// 力扣
// 我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求
// 按从小到大的顺序的第 n 个丑数。

// 牛客
// 把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑
// 数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。
// 求按从小到大的顺序的第N个丑数。

题解:

///

// 由题意可知,丑数n因子包括2 3 5,如果一个数能被2,3,5整除,那么连续将
// 其因子2,3,5除尽后(while (n%2 == 0): n = n/2...)得到的数为1。
// 这个数n就是丑数。我们大可以从1开始一个个判断每个整数是不是丑数

// 但是这样做效率太低了。
// 由于丑数因子相同,所以小丑数 × 2/3/5可以得到大丑数,这就可以用一个
// 数组存储丑数,利用动态规划使得我们从小丑数推断出大丑数。
// 注意要维持数组从小到大的性质。


// 力扣
// 执行用时: 2 ms, 在所有 Java 提交中击败了99.11% 的用户
// 内存消耗:37.3 MB, 在所有 Java 提交中击败了93.79%的用户
class Solution {
    public int nthUglyNumber(int n) {
		// for循环用于递归丑数数组的每一个位置,将该位置对应
		// 的丑数从小到大推断出来。由于丑数只包含2 3 5这三个因子,
		// 我们根据前面的小丑数,分别乘这三个因子就可以得到后面的大丑数,
		// i2,i3,i5用于前面“小丑数”的索引,dp[i2]用于乘2,dp[i3]用于乘3,
		// dp[i5]用于乘5,索引均初始化为0(数组第一位),
		// 由于要分别乘3个因子,所以会得到3个丑数,又因为需要升序,
		// 所以取乘出的三个丑数中最小的丑数作为当前遍历位置丑数即可。
		// 此时参加推断的索引右移一位。
		
		// 注意:再次强调,丑数的因子只有2,3,5,
		// 我们设置i2,i3,i5这三个索引从0开始右移,
		// 专门用于遍历索引乘2,3,5。也就是说所有的“小丑数”
		// 都有机会被i2,i3,i5遍历并乘2 3 5中的任意一个,来推导下一个丑数
		// 所以这个过程,就足以包含所有小丑数,推导出大丑数的所有情况(三种乘法)
        int i2 = 0, i3 = 0, i5 = 0;
		// 构建长度为n的数组,推断出的第n个元素(尾元素)就是我们要的元素
		int[] dp = new int[n];
		// 数组第一个丑数为1
		dp[0] = 1;
		for (int i = 1; i < n; i++) {
			int b2 = dp[i2] * 2, b3 = dp[i3] * 3, b5 = dp[i5] * 5;
			dp[i] = Math.min(b2, Math.min(b3, b5));
			if (dp[i] == b2)
				i2++;
			if (dp[i] == b3)
				i3++;
			if (dp[i] == b5)
				i5++;
		}
		return dp[n - 1];
    }
}

// 牛客
// 运行时间:11ms,超过88.19%用Java提交的代码
// 占用内存:9584KB,超过71.92%用Java提交的代码
public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if (index == 0)
            return 0;
        int[] dp = new int[index];
        dp[0] = 1;
        int i2 = 0, i3 = 0, i5 = 0;
        for (int i = 1; i < index; i++) {
            int b2 = dp[i2] * 2, b3 = dp[i3] * 3, b5 = dp[i5] * 5;
            dp[i] = Math.min(b2, Math.min(b3, b5));
            if (dp[i] == b2)
                i2++;
            if (dp[i] == b3)
                i3++;
            if (dp[i] == b5)
                i5++;
        }
        return dp[index - 1];
    }
}


【剑指offer】60. n个骰子的点数

题目描述:

在这里插入图片描述

在这里插入图片描述

// 60. n个骰子的点数


// 力扣
// 把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,
// 打印出s的所有可能的值出现的概率。
// 你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 
// 个骰子所能掷出的点数集合中第 i 小的那个的概率。

// 这题其实可理解为求所有掷出点数的概率

题解:

// 力扣
// 如果n=1个骰子,可能出现的值的和为[1-6]一共6种,每个值概率相同,
// 如果n=2个骰子,可能出现的值的和为[2-12]共11种,
// 和为s=2的概率为1/6 * 1/6,
// 和为s=3的概率为1/6 * 1/6 (1和2) + 1/6 * 1/6 (2和1)。
// 如果n=3个骰子,可能出现的值的和为[3-18]共16种,
// 和为s=3的概率为1/6 * 1/6 * 1/6,
// 和为s=4的概率为1/6 * 1/6 * 1/6 (1 1 2) + 1/6 * 1/6 * 1/6 (2 1 1)
// + 1/6 * 1/6 * 1/6 (1 2 1)
// 
// 可以看出,n个骰子的点数可以分解为n-1个骰子的点数遇到1个骰子的点数,
// 如此循环,其实是一个动态规划问题。
// 我们用数组下标作为骰子点数来理解,1个骰子的每一个点数出现的概率是1/6,
// 由于骰子从1开始,一路动态规划到n个骰子,所以我们将初始化n-1个骰子
// 概率为double[] pre = {1/6d, 1/6d, 1/6d, 1/6d, 1/6d, 1/6d}。
// 我们称pre为点数-概率计数数组,索引就是点数,元素就是点数(和)对应的概率
// 
// for循环从2到n遍历会递增的骰子数量,记为i,假设n=2,则此时i=n=2,
// 根据骰子数n构建目标概率数组temp,n=2可以分解为"n-1个"和"1个骰子",
// 第二层for循环遍历"n-1骰子"的概率pre[j],再第三层for循环遍历"1个骰子",
// 的概率(就是1/6,所以实际就是for循环遍历6次,每次乘1/6),
// 下标就是骰子点数,所以temp[j + t] += pre[j] * 1/6。
// 那么此时"n-1(n-1=1)个骰子"的第一个点数(j=0),遇到"1个骰子"的第一个
// 点数(t=0)(点数就是下标,两边都是第一个点数,所以点数之和是0+0
// 所以点数之和的概率是temp[0 + 0](j=t=0))
// 的概率是temp[0 + 0] = pre[j]*1/6 = 1/6*1/6。以此类推,
// 第三层for循环第一次完成之后,"n-1个骰子"的第一个点数,
// 遇到"1个骰子"的6个点数之和的概率都会被算出来,存入相应temp中,
// temp的索引就是相应的点数之和,temp的元素就是点数之和对应的概率。
//
// "n-1个骰子"的所有点数概率(pre同样也是,索引记录点数,元素记录概率)
// 之后都会和"1个骰子"的6个点数计算相遇概率,并将计算好的概率存入temp中
// 最后第二层循环完成,temp赋给pre,第一层for循环开始下一个骰子的计算。
//
// 所有循环完成,返回点数-概率计数数组pre。
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:38.6 MB, 在所有 Java 提交中击败了74.58%的用户
class Solution {
    public double[] dicesProbability(int n) {
		double[] pre = {1/6d, 1/6d, 1/6d, 1/6d, 1/6d, 1/6d};
		for (int i = 2; i <= n; i++) {
			double temp[] = new double[i*6 - i + 1];
			for (int j = 0; j < pre.length; j++) {
				for (int t = 0; t < 6; t++)
					temp[j + t] += pre[j] * 1/6;
			}
			pre = temp;
		}
		return pre;
    }
}



// 可以试着将过程打印出来
class Solution {
    public double[] dicesProbability(int n) {
		double[] pre = {1/6d, 1/6d, 1/6d, 1/6d, 1/6d, 1/6d};
		for (int i = 2; i <= n; i++) {
			double temp[] = new double[i*6 - i + 1];
			for (int j = 0; j < pre.length; j++) {
				arrPrint(temp);
				for (int t = 0; t < 6; t++)
					temp[j + t] += pre[j] * 1/6;
			}
			pre = temp;
		}
		return pre;
    }
	
	// 辅助函数:将int[] 打印出来
    private static void arrPrint(double[] arr) {
        StringBuilder str = new StringBuilder();
        str.append("[");
        for (double v : arr) {
            str.append(v + ", ");
        }
        str.delete(str.length() - 2, str.length());
        str.append("]");
        System.out.println(str.toString());
    }
}



【剑指offer】62. 圆圈中最后剩下的数字

题目描述:

在这里插入图片描述
在这里插入图片描述

// 62. 圆圈中最后剩下的数字


// 力扣
// 0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里
// 删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下
// 的最后一个数字。

// 例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个
// 数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。


// 牛客
// 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此
// 。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首
// 先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始
// 报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼
// 物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去..
// ..直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏
// 版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友
// 的编号是从0到n-1)

// 如果没有小朋友,请返回-1



题解:

// 力扣
// 数组的查找问题。n是数组长度,数组为[0, 1, .. , n-1],元素等于索引,
// 步长为m。首先是升序数组,但是看题意无法用二分查找,而是约瑟夫环问题。
// 
// 如果数组长度n=0,说明数组啥也没有,直接返回-1,
// 如果n=1,说明数组只有一个数0,剩下的数就是这个0,直接返回0。
// 
// 返回主函数的递归调用,递归调用下一次去掉数字的情况,
// 去掉一个数字之后,数组长度为n - 1(去掉谁不重要,重要的是剩下谁
// ,所以我们不关心新数组其他元素的内容,不特意去修改,而是关心
// 去掉数字之后数组的长度为n-1),步长依然是m。
// 如果题目中每一次去掉的数字,都是从头往右数第m个位置,
// 那下一个去掉的元素就是lastRemaining(n - 1, m)。但是根据题意,
// 下一个要去掉的元素要在上一个去掉元素的位置开始,再往右数。
// 而上一个次去除元素时已经右移m个位置了,
// 所以下一个要去掉的元素为 lastRemaining(n - 1, m) + m,
//                   总偏移量 = (从头往右的偏移量) + (上一个位置的偏移量)   
// 注意,(总偏移量) 需要处理成 (总偏移量)%n,来模拟进位
// 的情况,如果 (lastRemaining(n - 1, m) + m) 的总偏移量超过数组遍历的长度,
// 则需要除n取余,来获得进位后的索引元素,
// 而如果 (lastRemaining(n - 1, m) + m)的总偏移量不超过数组长度,那么
// (总偏移量)%n 就是(总偏移量)自己,则不进位,“无事发生”。
// 比如:n=5, m=3
// [0, 1, 2, 3, 4]
// 第一次到2位置,删除2,下一个位置是2 + m = 2 + 3 = 5,5已经超过数组
// 遍历长度,进位:5 % 5 = 0,则进位到0这个位置元素,所以下一个删除元素
// 是0,如此递归循环下去。最后剩下一个元素时,依靠之前的条件判断
// 跳出递归返回即可。

// 执行用时:11 ms, 在所有 Java 提交中击败了46.60%的用户
// 内存消耗:40.3 MB, 在所有 Java 提交中击败了31.54%的用户
class Solution {
    public int lastRemaining(int n, int m) {
        if (n == 0)
			return -1;
		if (n == 1)
			return 0;
		return (lastRemaining(n - 1, m) + m) % n;
    }
}




// 牛客
// 运行时间:11ms,超过92.79%用Java提交的代码
// 占用内存:9972KB,超过11.00%用Java提交的代码
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if (n == 0)
            return -1;
        if (n == 1)
            return 0;
        return (LastRemaining_Solution(n - 1, m) + m) % n;
    }
}


【剑指offer】64. 求1+2+…+n

题目描述:

在这里插入图片描述
在这里插入图片描述

// 64. 求1+2+…+n

// 力扣
// 求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch
// 、case等关键字及条件判断语句(A?B:C)。

// 牛客
// 求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、c
// ase等关键字及条件判断语句(A?B:C)。

题解:


// 力扣
// 【伪解法】
// 首先会想到递归方法,但是if语句不让用,咋办呢?
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:35.6 MB, 在所有 Java 提交中击败了71.67%的用户
class Solution {
    public int sumNums(int n) {
		if (n = 1)
			return 1;
		return sumNums(n - 1) + n;
    }
}


// 力扣
// 【正式方法】
// 这时候就需要换种方式终止递归条件。还记得&&运算具有短路功能,
// 如果&&之前的内容不成立,&&之后的内容将无法执行。
// 我们设置一个boolean值b,做(n > 0)和 A逻辑 的&&运算,
// 这个A包含sumNums递归调用的逻辑,A是什么不重要,重要的是它能够
// 递归起来,帮我们把运算算完。这里我们设置一个合理的A,
// A 为 (sum += sumNums(n - 1)) > 0。这样我们可以递归调用
// sumNums,并把返回值累加到sum中,sum将被初始化为主函数输入n。
//
// 递归到当n=1变为n=0时,b前面的(n > 0)不成立,&&运算截断,递归自动停止。
// 我们返回的sum就是我们要的1+2+..+n。
// 
// 注:A逻辑是什么不重要,重要的是它可以帮我们递归主函数
// 你甚至可以写成((sum += sumNums(n - 1)) < 0)。
class Solution {
    public int sumNums(int n) {
		int sum = n;
		boolean b = (n > 0) && ((sum += sumNums(n - 1)) > 0);
		return sum;
    }
}



// 牛客
// 运行时间:10ms超过86.46%用Java提交的代码
// 占用内存:9824KB超过1.60%用Java提交的代码
public class Solution {
    public int Sum_Solution(int n) {
        int sum = n;
        boolean b = (n > 0) && ((sum += Sum_Solution(n - 1)) > 0);
        return sum;
    }
}



特殊解——分治法 :

【剑指offer】16. 数值的整数次方

题目描述:

在这里插入图片描述

在这里插入图片描述

// 力扣
// 实现函数double Power(double base, int exponent),
// 求base的exponent次方。不得使用库函数,同时不需要考虑
// 大数问题。

 
// 牛客
// 给定一个double类型的浮点数x和int类型的整数exponent。
// 求x的exponent次方。
// 保证x和exponent不同时为0

题解:

// 直接法 //

// 牛客
// 运行时间:27ms
// 占用内存:10648k
public class Solution {
    public double Power(double x, int n) {
        if (x == 0)
            return 0;
        if (n == 0)
            return 1;
        if (n == 1)
            return x;

        if (n > 0) {
            double res = x;
            for (int i = 1; i < n; i++)
                res *= x;
            return res;
        }
        else {
            double res = 1/x;
            for (int i = 1; i < -n; i++)
                res *= 1/x;
            return res;
        }
	}
}
// 分治算法
// n个x相乘:x*x*...*x(n个)
// 其实可以分成(x*x*...*x)*(x*x*...*x)
// 				 (n/2个)     (n/2个)
// 以此类推,可以一直分下去,每次分治都减少一半。
// 需要特别注意的是,如果n是偶数直接分治就行。
// 如果n是奇数,还需要多乘一个x。

// 力扣
// 执行用时:1 ms, 在所有 Java 提交中击败了96.53%的用户
// 内存消耗:38.2 MB, 在所有 Java 提交中击败了5.87%的用户
public class Solution {
	public double Power(double x, int n) {
		if (x == 0) return 0;
			
		double res = 0;
		if (n > 0) {
			res = pow(x, n);
		}
		else {
			res = pow(1/x, n);
		}
		return res;
	}

	// 分治法n次方计算函数
	// 将n个x相乘,分为两个n/2个x相乘
	// 输入变量为:基数x,幂次n
	private double pow(double x, int n) {
		if (n == 0) return 1;  // 若n==0,返回1
		if (n == 1) return x;  // 若n==1,返回x,递归终止
		// 递归调用分治法函数,继续顺延分治计算,直到n为1
		double res = pow(x, n/2);  
		res = res * res;  // 实现分治逻辑,得到的res合并相乘
		if (n % 2 != 0)  // 如果是奇数(无法被2整除)
			res *= x;  // 多乘一个x
		return res;  // 最后返回res
	}
}

// 牛客
// 运行时间:22ms
// 占用内存:10652k
public class Solution {
    public double Power(double x, int n) {
        if (x == 0) return 0;

        double res = 0;
        if (n > 0) {
            res = pow(x, n);
        }
        else {
            res = pow(1/x, n);
        }
        return res;
    }

    private double pow(double x, int n) {
        if (n == 0) return 1;
        if (n == 1) return x;
        double res = pow(x, n/2);
        res = res * res;
        if (n % 2 != 0)  // 如果是奇数(无法被2整除)
            res *= x;
        return res;
    }
}


特殊解——位运算 :

【剑指offer】15. 二进制中1的个数

题目描述:

在这里插入图片描述
在这里插入图片描述

// 力扣
// 请实现一个函数,输入一个整数(以二进制串形式),输出该数二进
// 制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 
// 1。因此,如果输入 9,则该函数输出 2。

// 牛客
// 输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。

题解:

/// 逐次运算 ///

// 牛客
// 无法通过测试,超出时间限制
public class Solution {
    public int NumberOf1(int n) {
        if (n == 0)
            return 0;
        int index = 1;
        int count = 0;
        while (index != 0)
            if ((index & n) != 0)  // 与运算找1,如果当前位是0,与运算结果就是0,count不会递增
                count++;
            index = index << 1;  // index二进制整体左移一位
        return count;
    }
}
// 末位相消 //


// 最好还是用这种末位相消的方法来通过测试
// 假如一个二进制数010100,减1之后为010011,
// 可以看到,原来最右边的1变成了0
// 原来最右边的1再往右的所有0全变成了1。
// 如果让010100和010011做与运算,得到010000,
// 和原来010100比,010000最右边的1被与运算相消了
// 所以其实一个数n和n-1做与运算,可以消掉n最右边的1。

// 牛客
// 运行时间:10ms
// 占用内存:9556k
public class Solution {
	public int NumberOf1(int n) {
		if (n == 0)
			return 0;
		int count = 0;
		while (n != 0) {
			n = n&(n-1);
			count++;
		}
		return count;
	}
}

// 力扣
// 执行用时:1 ms, 在所有 Java 提交中击败了98.11%的用户
// 内存消耗:35.7 MB, 在所有 Java 提交中击败了43.89%的用户
public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
		if (n == 0)
			return 0;
		int count = 0;
		while (n != 0) {
			n = n&(n-1);
			count++;
		}
		return count;
    }
}



【剑指offer】65. 不用加减乘除做加法

题目描述:

在这里插入图片描述
在这里插入图片描述

// 65. 不用加减乘除做加法

// 力扣
// 写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”
// 、“/” 四则运算符号。

// 牛客
// 写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运
// 算符号。

题解:

 位运算 //

// 力扣
// 无进位情况下,位运算加法和异或逻辑运算相同,所以a ^ b表示没有进位的情况
// 下两数的和。有进位情况下,位运算加法和与逻辑运算相同(结果左移一位)
// 所以 (a & b) << 1 是进位情况下两数的和。
// 递归调用主函数add,将进位的加法结果和不进位的加法结果相加,
// 递归终止条件为当b==0,由于进位加法是与逻辑运算左移,
// 左移到最后会使得所有位为,即可停止。
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:35.3 MB, 在所有 Java 提交中击败了39.68%的用户
class Solution {
    public int add(int a, int b) {
		if (b == 0)
			return a;
		return add(a ^ b, (a & b) << 1);
    }
}


// 牛客
// 运行时间:13ms,超过58.96%用Java提交的代码
// 占用内存:9600KB,超过72.42%用Java提交的代码
public class Solution {
    public int Add(int num1,int num2) {
        if (num2 == 0)
            return num1;
        return Add(num1 ^ num2, (num1 & num2) << 1);
    }
}



特殊解——贪心算法 :

【剑指offer】14.1. 剪绳子

题目描述:

在这里插入图片描述
在这里插入图片描述


// 力扣
// 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段
// (m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0]
// ,k[1]...k[m-1] 。请问 k[0]*k[1]*...*k[m-1] 可能的最大
// 乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度
// 分别为2、3、3的三段,此时得到的最大乘积是18。

// 牛客
// 给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数
// ,n>1并且m>1,m<=n),每段绳子的长度记为k[1],...,k[m]。请问k
// [1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我
// 们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

题解:

/ 动态规划 //
// 牛客
// 动态规划解不难,但是需要换个思路来理解。这时就可以把题目理解成,
// "找出整数n能够被其他小于n的正整数k[i](i>=0)加起来的所有组合(k[0],k[1],...,k[m]),
// 在这个过程中,再找出这些组合里乘积最大的,并返回这个乘积。"
// 把这个问题抽象出来,再动态规划求解:
// 对于整数n,n的组合数,
// 不仅是第一个组合数可以取任何值,第二个组合数也可以取任何值,
// 在动态规划题目《青蛙跳台阶》问题中,青蛙只能跳1阶或者2阶,
// 也就是说组合数只能要么是1,要么是2。
// 而在题目《变态跳台阶》问题中,这个青蛙可以跳1阶,也可以直接跳n阶
// 组合数可以取任意值。所以其实这道题的动态规划解,跟《变态跳台阶》问题很像。
// 但是中途要保存组合数最大的积,且组合数相加不超过n。

// 组合数可以取1,剩下n-1,有f(n-1)种可能
// 可以取2,剩下n-2,有f(n-2)种可能
// ...
// 且有f(n)=f(1)+f(2)+...+f(k1-1)+f(k1),(1+2+..+k1=n, 1<k1<n)
// f(k1)=f(1)+f(2)+..+f(k2-2)+f(k2-1),(1+2+..+k2=k1, 1<k2<k1)
// ...
// f(2)=1

// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:35.3 MB, 在所有 Java 提交中击败了60.53%的用户
class Solution {
	public int cuttingRope(int n) {
		if (n < 2)
			return 0;
		if (n == 2)
			return 1;
        if (n == 3)
            return 2;
		int[] f = new int[n + 1];
		f[1] = 1;
        f[2] = 2;
        f[3] = 3;
		for (int i = 4; i <= n; i++) {
            int max = 0;
			for (int j = 1; j <= i/2; j++) {
				if (max < f[j]*f[i-j])
					max = f[j]*f[i-j];
			}
            f[i]=max;
		}
		return f[n];
	}
}

// 执行用时:1 ms, 在所有 Java 提交中击败了42.32%的用户
// 内存消耗:35.3 MB, 在所有 Java 提交中击败了62.66%的用户
class Solution {
	public int cuttingRope(int n) {
		if (n < 2)
			return 0;
		int[] f = new int[n + 1];
		f[1] = 1;
		for (int i = 2; i <= n; i++) {
			for (int j = 1; j < i; j++) {
				if (f[j] <= j && f[i] < j*(i-j))
					f[i] = j*(i-j);
				if (f[j] > j && f[i] < f[j]*(i-j))
					f[i] = f[j]*(i-j);
			}
		}
		return f[n];
	}
}

// 牛客
// 运行时间:12ms
// 占用内存:9732k
public class Solution {
    public int cutRope(int n) {
        if (n < 2)  // 题目已经说了会大于等于2,但还是习惯了写这个
            return 0;
        int[] f = new int[n + 1];
        f[1] = 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < i; j++) {
                if (f[j] <= j && f[i] < j*(i-j))
                    f[i] = j*(i-j);
                if (f[j] > j && f[i] < f[j]*(i-j))
                    f[i] = f[j]*(i-j);
            }
        }
        return f[n];
    }
}


 贪心算法 /

// 需要记住的是:这类问题尽可能拆分出长度为3的绳子,
// 直到拆不出3,那就拆分成2,此时的总乘积就是最大的

// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:35.5 MB, 在所有 Java 提交中击败了33.21%的用户
class Solution {
	public int cuttingRope(int n) {
        if (n < 2)
			return 0;
		if (n == 2)
			return 1;
		if (n == 3)
			return 2;
		int to3 = n / 3;  // n/3,表示n中分理出to3这么多个3
		// n如果能被3整除,那么to3=n/3, to3*3=n,所以n-to3*3==0
		// 如果n不能被3整除,n-to3即为n/3的余数
		// 如果n/3余数为1,则to3减1
		// 如果n/3余数为2,则to3不变。(n/3,余数要么是1要么是2)
		if (n - to3 * 3 == 1)
			to3--;
		// 如果n/3的余数(n - to3*3)为1,to3减1相当于放回一个3给(n - to3*3)
		// 此时(n - to3*3)=4
		// 如果n/3的余数(n - to3*3)为2,则to3不变,(n - to3*3)=2
		int to2 = (n - to3 * 3) / 2;
		// 分离好了2和3,直接返回3^to3 * 2^to2(to3个3相乘,乘to2个2相乘)
		return (int) (Math.pow(3, to3)) * (int) (Math.pow(2, to2));
	}
}


// 牛客
// 运行时间:12ms
// 占用内存:9716k
public class Solution {
    public int cutRope(int n) {
        if (n < 2)
			return 0;
		if (n == 2)
			return 1;
		if (n == 3)
			return 2;
		int to3 = n / 3; 
		if (n - to3 * 3 == 1)
			to3--;
		int to2 = (n - to3 * 3) / 2;
		return (int) (Math.pow(3, to3)) * (int) (Math.pow(2, to2));
    }
}


【剑指offer】14.2. 剪绳子 II

题目描述:

在这里插入图片描述

// 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整
// 数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问
// k[0]*k[1]*...*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度
// 是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

// 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,
// 请返回 1。

题解:

/ 贪心算法 //
// 思路和剪绳子 I一样的,但是需要把过程分离,
// 每次乘3或乘2都要检查一下是否溢出
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:35.2 MB, 在所有 Java 提交中击败了81.56%的用户
class Solution {
	public int cuttingRope(int n) {
		if (n < 4) {
			return n - 1;
		}
		else if (n == 4) {
			return n;
		}
		long res = 1;;
		while (n > 4) {
			res *= 3;
			res %= 1000000007;
			n -= 3;
		}
		return (int) (res*n%1000000007);
	}
}



特殊解——动态规划 :

【剑指offer】46. 把数字翻译成字符串

题目描述:

在这里插入图片描述

在这里插入图片描述

// 46. 把数字翻译成字符串


// 力扣
// 给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,
// 1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可
// 能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同
// 的翻译方法。

题解:

/// 动态规划 
// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:35.2 MB, 在所有 Java 提交中击败了66.89%的用户
class Solution {
    public int translateNum(int num) {
		// num转为String类型用于遍历num中每个数字,记为s。
		// 之后所有操作都基于s。
		String s = Integer.toString(num);
		if (s == null || s.length() == 0)
			return 0;
		int len = s.length();  // 取s长度
		// dp数组,根据s[i-2]和s[i-1](实际没有s[i-2]和s[i-1])判断dp[i]
		// 比如dp[2]表示s中前两个元素的组合数
		int[] dp = new int[len + 1]; 
		dp[0] = 1;  // 初始化
		dp[1] = 1;
		for (int i = 2; i <= len; i++) {
			// 结合s[i-2]和s[i-1]的情况(记为temp),
			// 用dp[i-2] dp[i-1]来判断dp[i]
			String temp = s.substring(i - 2, i);
			// 如果temp中数字大于10,小于25,说明可以组合
			// dp[i]为dp[i - 2]和dp[i - 1]之和。
			if (temp.compareTo("10") >= 0 && temp.compareTo("25") <= 0)
				dp[i] = dp[i - 2] + dp[i - 1];
			else 
				dp[i] = dp[i - 1];  // 否则不能组合,无法新增组合数
		}
		return dp[len];  // 最后返回dp结果,后一状态取决于前一状态,尾
						 // 元素即为最终状态
    }
}


// 力扣
// 简略写法
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:34.9 MB, 在所有 Java 提交中击败了97.16%的用户
class Solution {
    public int translateNum(int num) {
		String s = Integer.toString(num);
		if (s == null || s.length() == 0)
			return 0;
		int len = s.length();
		int[] dp = new int[len + 1];
		dp[0] = 1;
		dp[1] = 1;
		for (int i = 2; i <= len; i++) {
			String temp = s.substring(i - 2, i);  
			dp[i] = temp.compareTo("10") >= 0 && temp.compareTo("25") <= 0 ? dp[i-2] + dp[i-1] : dp[i-1];
		}
		return dp[len];
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/25331.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用反射设计通用的实例化对象方案

需求 对象的相关信息存储在javabean.properties文件中&#xff0c;通过读取properties文件中的信息&#xff0c;实例化对象&#xff0c;要求程序不能硬编码&#xff0c;即程序可以通用&#xff0c;针对不同的对象&#xff0c;都可以实例化。仅需修改配置文件&#xff0c;不需要…

【课代表笔记】直播回顾:Top药企的数字化实践集锦

【K讲了】系列直播之医药行业第一期&#xff1a;Top药企的数字化实践集锦前不久已在视频号和大家如期见面&#xff0c;以下是课代表为大家抄好的笔记~~ 斯歌K2的医药行业经验 K2在医药领域拥有丰富的客户积累及实施经验&#xff0c;全球TOP 10药企中有7家选择K2。斯歌K2已在医药…

数据链路层:封装成帧

1.数据链路层&#xff1a;封装成帧 笔记来源&#xff1a; 湖科大教书匠&#xff1a;封装成帧 声明&#xff1a;该学习笔记来自湖科大教书匠&#xff0c;笔记仅做学习参考 封装成帧是指数据链路层给上层交付的协议数据单元添加帧头和帧尾使之成为帧 帧头和帧尾中包含重要的控制…

java的UDP(一)

文章目录 1. 简介2. UDP客户端3. UDP服务器4. DatagramPacket类 1. 简介 Java中的UDP实现分为两个类&#xff1a;DatagramPacket和DatagramSocket。DatagramPacket类将数据字节填充到UDP包汇总&#xff0c;这称为数据报&#xff0c;由你来解包接收的数据报。DatagramSocket可以…

Day57【动态规划】647.回文子串、516.最长回文子序列

647.回文子串 力扣题目链接/文章讲解 视频讲解 1、确定 dp 数组下标及值含义 dp[i][j]&#xff1a;表示区间范围为 [i, j] 的子串是否为回文串&#xff08;j > i&#xff09; 这样定义才方便我们的递推&#xff01;怎么想到的&#xff1f;回文串需要对比串的两端&#…

用可编程逻辑器件FPGA LCMXO2-4000HC-6MG132I 实现智能汽车解决方案设计

LCMXO2-4000HC-6MG132I lattice莱迪斯深力科 MachXO2 可编程逻辑器件 (PLD) 由六个超低功耗、即时启动、非易失性 PLD 组成&#xff0c;可提供 256 至 6864 个查找表 (LUT) 的密度。 MachXO2 系列 PLD 提供多种特性&#xff0c;例如嵌入式块 RAM (EBR)、分布式 RAM 和用户闪存 …

基于html+css的图片展示93

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

Spring Boot注解@Async与线程池的配置

目录 使用异步注解创建异步任务 Async注解 使用Demo 线程池配置 Spring Boot默认用于异步任务线程池配置 线程池配置 线程池隔离 为什么需要线程池隔离&#xff1f; 线程池隔离实现Demo 线程池配置&#xff1a; 异步任务&#xff1a; 测试demo 参考内容&#xff1a; 使…

Modern CSV:大型 CSV 文件编辑器/查看器 Crack

Modern CSV用于快速查看大型 CSV 文件 适用于 Windows、Mac 和 Linux 的复杂 CSV 编辑器/查看器 被使用 电子商务运营商。数据科学家。会计师。 IT 专业人员。学生。医学研究人员。数字营销人员。生物学家。工程师。 现代 CSV 是适用于 Windows、Mac 和 Linux 的功能强大的表格…

面向对象编程 实验三 sduwh 子窗口与控件的基本用法、资源的使用 参考实验报告1

源自网络收集&#xff0c;仅供参考 实验三收集到两份完整报告&#xff0c;这是其一&#xff0c;另一份见本专栏下一篇文章。 实验题目 《面向对象程序设计》 实验三 实验题目&#xff1a;子窗口与控件的基本用法、资源的使用 整体目的&#xff1a;理解、窗口之间的消息传送…

【Netty】Netty中的超时处理与心跳机制(十九)

文章目录 前言一、超时监测二、IdleStateHandler类三、ReadTimeoutHandler类四、WriteTimeoutHandler类五、实现心跳机制5.1. 定义心跳处理器5.2. 定义 ChannelInitializer5.3. 编写服务器5.4. 测试 结语 前言 回顾Netty系列文章&#xff1a; Netty 概述&#xff08;一&#…

内存栈与CPU栈机制

1. 内存栈: 先入后出,LIFO(LAST IN FIRST OUT) 入栈:将一个新的元素放到栈顶 出栈:从栈顶取出一个元素 栈顶元素总是最后一个入栈,需要时出栈. 2.CPU栈机制 8086CPU提供相关指令以栈方式来访问内存空间.相当于将一段内存当做栈来使用 8086CPU提供的入栈指令为:PUSH ,出栈指令为…

JointJS+ v3.7 Crack

JointJS v3.7 改进了对 SVG 上下文中的外部对象的支持。 2023 年 5 月 30 日 - 16:00 新版本 特征 改进了对外部对象 (HTML) 的支持- 外部对象已成为 Web 开发的标准&#xff0c;JointJS 现在已经在 SVG 上下文中引入了对外部对象的全面且功能齐全的支持。这意味着您现在可以在…

Elasticsearch 8.8.0 发布

Elasticsearch 是一个基于 Lucene 库的搜索引擎。它提供了一个分布式、支持多租户的全文搜索引擎&#xff0c;具有 HTTP Web 接口和无模式 JSON 文档。Elasticsearch 基于 Java 开发&#xff0c;并在 SSPL Elastic License 双重授权许可下作为开源软件发布。 Elasticsearch 8…

win10系统如何设置虚拟回环

在日常生活中&#xff0c;人们(特别是IT行业者)通常需要在一台机上进行软件测试&#xff0c;而同一台计算上通常只能使用一个地址&#xff0c;而在需要同时使用两个地址进行测试的时候就显得捉襟见肘。此方法通过配置window10自带的环回适配器&#xff0c;达到上述目的。 win1…

如何使用Kali进行信息收集?

渗透测试即模拟黑客入侵的手段对目标网络进修安全测试&#xff0c;从而发现目标网络的漏洞&#xff0c;对目标网络进行安全加固与漏洞修复。 Kali 是一个基于 debian 的渗透测试平台&#xff0c;其中集成了很多常见的和不常见的渗透测试工具&#xff0c;如下图&#xff1a; 工…

基于SSM的服装设计供需系统设计与实现

摘 要&#xff1a;作为服装设计的重要形式之一&#xff0c;服装具有显著的审美性&#xff0c;是人类情感表达不可忽视的代表形态。但在新时期背景下&#xff0c;随着服装设计的进一步优化&#xff0c;服装设计创新融合强度也随之增强。本文就服装设计供需系统进行深入探究。 服…

Linux之命令搜索

目录 Linux之命令搜索 Whereis命令 定义 基本信息 举例 which命令 定义 与whereis命令的区别 基本信息 举例 locate 命令 定义 优点 缺点 基本信息 案例 Linux之命令搜索 Whereis命令 定义 whereis --- 搜索系统命令的命令&#xff08;像绕口令一样&#xff09…

数据库新闻速递 明白3中主流的数据迁移方法 (译)

头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共8…

ShardingSphere笔记(三):自定义分片算法 — 按月分表·真·自动建表

ShardingSphere笔记&#xff08;二&#xff09;&#xff1a;自定义分片算法 — 按月分表真自动建表 文章目录 ShardingSphere笔记&#xff08;二&#xff09;&#xff1a;自定义分片算法 — 按月分表真自动建表一、 前言二、 Springboot 的动态数据库三、 实现我们自己的动态数…