难度中等163
实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,x^n)。不得使用库函数,同时不需要考虑大数问题。
示例 1:
输入:x = 2.00000, n = 10
输出:1024.00000
示例 2:
输入:x = 2.10000, n = 3
输出:9.26100
示例 3:
输入:x = 2.00000, n = -2
输出:0.25000
解释:2**-2 = 1/2**2 = 1/4 = 0.25
提示:
-100.0 < x < 100.0
-231 <= n <= 231-1
-104 <= xn <= 104
思路一:递归
递归是比较好理解的
如果n == 0,返回1
如果n < 0,最终结果为 1/x^{-n}
如果n为奇数,最终结果为 x * x ^ {n - 1}
如果n为偶数,最终结果为 x ^ {2*(n/2)}
class Solution {
public double myPow(double x, int n) {
if(n == 0){
return 1;
}else if(n < 0){
return 1 / (x * myPow(x, - n - 1));
}else if(n % 2 == 1){
return x * myPow(x, n - 1);
}else{
return myPow(x * x, n / 2);
}
}
}
注:Java中因为n的最小值可以取到 Integer.MIN_VALUE,如果直接取它的相反数的话还是它自己,会导致堆栈溢出,因此提一个x出来,具体看代码——避免溢出
剑指 Offer 17. 打印从1到最大的n位数
难度简单128
输入数字 n
,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
说明:
- 用返回一个整数列表来代替打印
- n 为正整数
这里如果题目中说明的是返回整数数组,那么以下的方法就有效,
class Solution {
public int[] printNumbers(int n) {
int end = (int)Math.pow(10, n) - 1;
int[] res = new int[end];
for(int i = 0; i < end; i++)
res[i] = i + 1;
return res;
}
}
如果题目是返回的是字符串数组,如果考点是大数越界情况下的打印。需要解决以下三个问题:
-
表示大数的变量类型:
无论是 short / int / long … 任意变量类型,数字的取值范围都是有限的。因此,大数的表示应用字符串 String 类型。 -
生成数字的字符串集:
使用 int 类型时,每轮可通过 +1生成下个数字,而此方法无法应用至 String 类型。并且, String 类型的数字的进位操作效率较低,例如 “9999” 至 “10000” 需要从个位到千位循环判断,进位 4 次。观察可知,生成的列表实际上是 n 位 00 - 99 的 全排列 ,因此可避开进位操作,通过递归生成数字的 String 列表。
-
递归生成全排列:
基于分治算法的思想,先固定高位,向低位递归,当个位已被固定时,添加数字的字符串。例如当 n = 2 时(数字范围 1 - 99 ),固定十位为 00 - 99 ,按顺序依次开启递归,固定个位 0 - 9 ,终止递归并添加数字字符串。
下面的递归迭代具备代表性意义,这类题目多多练手理解深刻!!!!
class Solution {
StringBuilder res;
int count = 0, n;
char[] num, loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
public String printNumbers(int n) {
this.n = n;
res = new StringBuilder(); // 数字字符串集
num = new char[n]; // 定义长度为 n 的字符列表
dfs(0); // 开启全排列递归
res.deleteCharAt(res.length() - 1); // 删除最后多余的逗号
return res.toString(); // 转化为字符串并返回
}
void dfs(int x) {
if(x == n) { // 终止条件:已固定完所有位
res.append(String.valueOf(num) + ","); // 拼接num 并添加至 res 尾部,使用逗号隔开
return;
}
for(char i : loop) { // 遍历 ‘0‘ - ’9‘
num[x] = i; // 固定第 x 位为 i
dfs(x + 1); // 开启固定第 x + 1 位
}
}
}
为了避免数字开头出现0,先把首位first固定,first取值范围为1~9
用digit表示要生成的数字的位数,本题要从1位数一直生成到n位数,对每种数字的位数都生成一下首位,所以有个双重for循环
生成首位之后进入递归生成剩下的digit - 1位数,从0~9中取值
递归的中止条件为已经生成了digit位的数字,即index == digit,将此时的数num转为int加到结果res中
class Solution {
int[] res;
int count = 0;
public int[] printNumbers(int n) {
res = new int[(int)Math.pow(10, n) - 1];
for(int digit = 1; digit < n + 1; digit++){
for(char first = '1'; first <= '9'; first++){
char[] num = new char[digit];
num[0] = first;
dfs(1, num, digit);
}
}
return res;
}
private void dfs(int index, char[] num, int digit){
if(index == digit){
res[count++] = Integer.parseInt(String.valueOf(num));
return;
}
for(char i = '0'; i <= '9'; i++){
num[index] = i;
dfs(index + 1, num, digit);
}
}
}
解法:以下代码来自班上小哥哥,也是大数解法,写的很好
public String printNumbers(int n) {
StringBuilder cache=new StringBuilder();
List<String> ans=new ArrayList<>();
int i=0;
BIGCore(cache,ans,0,n);
StringBuilder stringBuilder=new StringBuilder();
for (String a:ans)
{
stringBuilder.append(a+",");
}
stringBuilder.deleteCharAt(stringBuilder.length()-1);
return stringBuilder.toString();
}
public void BIGCore(StringBuilder cache, List<String> ans, int i, int n){
if (i==n) {
if (cache.length() > 0)
ans.add(cache.toString());
}
else for (int num = 0;num<=9;num++){
if (num!=0 || cache.length()>0) {
cache.append(num);
BIGCore(cache, ans, i + 1, n);
cache.deleteCharAt(cache.length()-1);
}
else
BIGCore(cache, ans, i + 1, n);
}
上面的代码,思想都是迭代,一种是利用char[]数组,每一轮迭代时进行替换,后一种就是利用SringBuilder,在每一轮迭代时先delete最后一个元素,再进行append。
在这里迭代的思想可以好好参悟,一起加油吧!
剑指 Offer 18. 删除链表的节点
难度简单
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
**注意:**此题对比原题有改动
示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
说明:
- 题目保证链表中节点的值互不相同
- 若使用 C 或 C++ 语言,你不需要
free
或delete
被删除的节点
解法一:单节点:
- 下面的代码仅仅区分了头节点,头结点以后都是head.next节点,故一并考虑,解法更胜一筹
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if (head == null) return null;
if (head.val == val) return head.next;
ListNode cur = head;
while (cur.next != null && cur.next.val != val)
cur = cur.next;
if (cur.next != null)
cur.next = cur.next.next;
return head;
}
}
推荐解法二:双节点(前后驱节点):
本题删除值为 val 的节点分需为两步:定位节点、修改引用。
定位节点: 遍历链表,直到 head.val == val 时跳出,即可定位目标节点。
修改引用: 设节点 cur 的前驱节点为 pre ,后继节点为 cur.next ;则执行 pre.next = cur.next ,即可实现删除 cur 节点。
算法流程:
特例处理(算法题中的边界条件,是做一道算法题首先需要考虑的,希望谨慎): 当应删除头节点 head 时,直接返回 head.next 即可。
初始化: pre = head , cur = head.next 。
这里pre是前节点,cur是后节点。
定位节点: 当 cur 为空 或 cur 节点值等于 val 时跳出。
保存当前节点索引,即 pre = cur 。
遍历下一节点,即 cur = cur.next 。
删除节点: 若 cur 指向某节点,则执行 pre.next = cur.next ;若 cur 指向 null,代表链表中不包含值为 val 的节点。
返回值: 返回链表头部节点 head 即可。
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head.val == val) return head.next;
ListNode pre = head, cur = head.next;
while(cur != null && cur.val != val) {
pre = cur;
cur = cur.next;
}
if(cur != null) pre.next = cur.next;
return head;
}
}
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。
提示:
0 <= nums.length <= 50000
1 <= nums[i] <= 10000
解法一:首尾双指针
定义头指针 left,尾指针 right.
left 一直往右移,直到它指向的值为偶数
right 一直往左移, 直到它指向的值为奇数
交换 nums[left] 和 nums[right].
重复上述操作,直到 left == right。
bingo自己写的啦
public int[] exchange(int[] nums) {
int i=0;
int j=nums.length-1;
int temp;
while (i<j){
while (i<j&&nums[i]%2==1)
i++;
while (i<j&&nums[j]%2==0)
j--;
temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
return nums;
}
解法二:快慢双指针
定义快慢双指针 fast 和 low ,fast 在前, low 在后 .
fast的作用是向前搜索奇数位置,low 的作用是指向下一个奇数应当存放的位置
fast 向前移动,当它搜索到奇数时,将它和 nums[low] 交换,此时 low 向前移动一个位置 .
重复上述操作,直到 fast 指向数组末尾 .
C++的代码,不妨碍理解
class Solution {
public:
vector<int> exchange(vector<int>& nums) {
int low = 0, fast = 0;
while (fast < nums.size()) {
if (nums[fast] & 1) {
swap(nums[low], nums[fast]);
low ++;
}
fast ++;
}
return nums;
}
};
剑指 Offer 18. 删除链表的节点
难度简单
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
**注意:**此题对比原题有改动
示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
说明:
- 题目保证链表中节点的值互不相同
- 若使用 C 或 C++ 语言,你不需要
free
或delete
被删除的节点
解法一:单节点:
- 下面的代码仅仅区分了头节点,头结点以后都是head.next节点,故一并考虑,解法更胜一筹
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if (head == null) return null;
if (head.val == val) return head.next;
ListNode cur = head;
while (cur.next != null && cur.next.val != val)
cur = cur.next;
if (cur.next != null)
cur.next = cur.next.next;
return head;
}
}
解法二:双节点):
本题删除值为 val 的节点分需为两步:定位节点、修改引用。
1、定位节点: 遍历链表,直到 head.val == val 时跳出,即可定位目标节点。
2、修改引用: 设节点 cur 的前驱节点为 pre ,后继节点为 cur.next ;则执行 pre.next = cur.next ,即可实现删除 cur 节点。
算法流程:
特例处理: 当应删除头节点 head 时,直接返回 head.next 即可。
初始化: pre = head , cur = head.next 。
定位节点: 当 cur 为空 或 cur 节点值等于 val 时跳出。
保存当前节点索引,即 pre = cur 。
遍历下一节点,即 cur = cur.next 。
删除节点: 若 cur 指向某节点,则执行 pre.next = cur.next ;若 cur 指向 null,代表链表中不包含值为 val 的节点。
返回值: 返回链表头部节点 head 即可。
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head.val == val) return head.next;
ListNode pre = head, cur = head.next;
while(cur != null && cur.val != val) {
pre = cur;
cur = cur.next;
}
if(cur != null) pre.next = cur.next;
return head;
}
}
剑指 Offer 22. 链表中倒数第k个节点
难度简单
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6
个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6
。这个链表的倒数第 3
个节点是值为 4
的节点。
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
解法:双指针
解题思路:
第一时间想到的解法:
先遍历统计链表长度,记为 n ;
设置一个指针走 (n-k) 步,即可找到链表倒数第 k 个节点。
使用双指针则可以不用统计链表长度。
算法流程:
初始化: 前指针 former 、后指针 latter ,双指针都指向头节点 head 。
构建双指针距离: 前指针 former 先向前走 k步(结束后,双指针 former 和 latter 间相距 k 步)。
双指针共同移动: 循环中,双指针 former 和 latter 每轮都向前走一步,直至 former 走过链表 尾节点 时跳出(跳出后, latter 与尾节点距离为 k-1,即 latter 指向倒数第 k 个节点)。
返回值: 返回 latter 即可。
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode former = head, latter = head;
for(int i = 0; i < k; i++)
former = former.next;
while(former != null) {
former = former.next;
latter = latter.next;
}
return latter;
}
}
//以下是自己写的,思考类似:
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast=head;
ListNode low=head;
int order=1;
while (order<k){
fast=fast.next;
order++;
}
while (fast!=null){
fast=fast.next;
low=low.next;
}
return low;
}
剑指 Offer 24. 反转链表
难度简单
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
限制:
0 <= 节点个数 <= 5000
思路一:迭代
迭代需要三个指针,pre,cur,tmp,分别按顺序指向三个节点
三个指针的初始化:
pre指向空节点,cur指向头结点head,tmp指向head.next,因为head.next可能不存在,tmp在循环中定义,这样如果head为空就不会进入循环
迭代过程
tmp指向cur.next
cur指向pre
pre移动到cur位置
cur移动到tmp位置
当cur为空时,返回pre
做了几次,结合图看,恍然大悟!!!
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head, pre = null;
while(cur != null) {
ListNode tmp = cur.next; // 暂存后继节点 cur.next
cur.next = pre; // 修改 next 引用指向
pre = cur; // pre 暂存 cur
cur = tmp; // cur 访问下一节点
}
return pre;
}
}
剑指 Offer 25. 合并两个排序的链表
难度简单124
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:
0 <= 链表长度 <= 1000
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dum = new ListNode(0), cur = dum;
while(l1 != null && l2 != null) {
if(l1.val < l2.val) {
cur.next = l1;
l1 = l1.next;
}
else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 != null ? l1 : l2;
return dum.next;
}
}
剑指 Offer 26. 树的子结构
难度中等
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/ \
4 5
/ \
1 2`
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
示例 1:
输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:
输入:A = [3,4,5,1,2], B = [4,1]
输出:true
限制:
0 <= 节点个数 <= 10000
解题思路:
若树 B 是树 A 的子结构,则子结构的根节点可能为树 A 的任意一个节点。因此,判断树 B 是否是树 A 的子结构,需完成以下两步工作:
先序遍历树 A 中的每个节点 nA(对应函数 isSubStructure(A, B))
判断树 A 中 以 n A 为根节点的子树 是否包含树 B 。(对应函数 recur(A, B))
recur(A, B) 函数:
终止条件:
当节点 B 为空:说明树 B 已匹配完成(越过叶子节点),因此返回 true ;
当节点 A 为空:说明已经越过树 A 叶子节点,即匹配失败,返回false ;
当节点 A 和 B 的值不同:说明匹配失败,返回false ;
返回值:
判断 A 和 B 的左子节点是否相等,即 recur(A.left, B.left) ;
判断 A 和 B 的右子节点是否相等,即 recur(A.right, B.right) ;
isSubStructure(A, B) 函数:
特例处理: 当 树 A 为空 或 树 B 为空 时,直接返回false ;
返回值: 若树 B 是树 A 的子结构,则必满足以下三种情况之一,因此用或 || 连接;
以 节点 A 为根节点的子树 包含树 B ,对应 recur(A, B);
树 B 是 树 A 左子树 的子结构,对应 isSubStructure(A.left, B);
树 B 是 树 A 右子树 的子结构,对应 isSubStructure(A.right, B);
以上 2. 3. 实质上是在对树 A 做 先序遍历 。
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B));
}
boolean recur(TreeNode A, TreeNode B) {
if(B == null) return true;
if(A == null || A.val != B.val) return false;
return recur(A.left, B.left) && recur(A.right, B.right);
}
}
这些代码来源于力扣官网和一些大神的解答,如若侵权,本人定删。