第四部分:递归
231. 2的幂 (简单)
题目:给你一个整数 n
,请你判断该整数是否是 2 的幂次方。如果是,返回 true
;否则,返回 false
。
如果存在一个整数 x
使得 n == 2x
,则认为 n
是 2 的幂次方。
示例 1:
输入:n = 1 输出:true 解释:20 = 1
示例 2:
输入:n = 16 输出:true 解释:24 = 16
第一种思路:
还是往递归方面靠,结果官方又是没有给出递归的解。。。
思路应该还是没有问题,就是会超限。
思路总结:
基本知识:
2 的幂的特性是:只有 1、2、4、8、16 等能被表示为 2n2n 的数。
通过逐步翻倍,可以判定一个数是否是 2 的幂。因为我的想法是如果用从除法会要考虑一下计算机除数取整的问题。
递归检查:
首先检查基准条件。
如果 n 为 0,直接返回 false。
如果 n 是 1 或 2,返回 true,因为它们是 2 的幂。
如果 n 小于当前的检查值 (
pro
),返回 false,说明 n 不是 2 的幂。如果 n 等于
pro
,返回 true,说明 n 是 2 的幂。自我递增:
如果以上条件都不满足,将
pro
值翻倍,然后再次调用isPowerOfTwo
方法,继续检查。终止条件:
当
pro
大于 n 而且 n 也没有在之前的检查中被认定为 2 的幂时,最终会返回 false。
class Solution {
// 初始化 pro 为 4,每次递归调用中 pro 会乘以 2
int pro = 4;
public boolean isPowerOfTwo(int n) {
// 如果 n 等于 0,返回 false,因为 0 不是 2 的幂
if (n == 0) return false;
// 如果 n 等于 1 或 2,返回 true,因为 1 和 2 是 2 的幂
else if (n == 1 || n == 2) return true;
// 如果 n 小于 pro,返回 false,因为 n 不能是大于 2 的幂
else if (n < pro) return false;
// 如果 n 等于 pro,返回 true,因为 n 是 2 的幂
else if (n == pro) return true;
// 将 pro 倍增,以便检查下一个 2 的幂
pro = pro * 2;
// 递归调用,以判断 n 是否是 2 的幂
return isPowerOfTwo(n);
}
}
第二种思路:
这种是官方给出的解题思路,我觉得有必要了解并掌握,以前确实没有想过这个问题。
一个数 n 是 2 的幂,当且仅当 n 是正整数,并且 n 的二进制表示中仅包含 1 个 1。
因为二进制是逢二进一。
所有考虑使用位运算,将 n 的二进制表示中最低位的那个 1 提取出来,再判断剩余的数值是否为 0 即可。
有两种常见的与「二进制表示中最低位」相关的位运算技巧。
第一个技巧是:
n & ( n - 1 )
其中 & 表示按位与运算。该位运算技巧可以直接将 n 二进制表示的最低位 1 移除.
原理直接用LeetCode的解释了,我觉得他讲的听清楚的,其实也就是为了判断想与后为0即可。
解释:
因此,如果 n 是正整数并且 n & (n - 1) = 0,那么 n 就是 2 的幂。
class Solution {
public boolean isPowerOfTwo(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
}
第二个技巧是:
n & ( - n )
其中 −n 是 n 的相反数,是一个负数。该位运算技巧可以直接获取 n 二进制表示的最低位的 1。
由于负数是按照补码规则在计算机中存储的,−n 的二进制表示为 n 的二进制表示的每一位取反再加上 1,
原理:
因此,如果 n 是正整数并且 n & (-n) = n,那么 n 就是 2 的幂。
class Solution {
public boolean isPowerOfTwo(int n) {
return n > 0 && (n & -n) == n;
}
}
234.回文链表(简单)
题目:给你一个单链表的头节点 head
,请你判断该链表是否为回文链表。如果是,返回 true
;否则,返回 false
。
示例 1:
输入:head = [1,2,2,1] 输出:true
第一种思路:
这道题一开始没想到用递归怎么做,所以没继续往哪方面思考,然后想到之前不是写过数组的回文序列嘛,化未知为已知,所以可以先把链表中的数值先取出来放在LIst数组中。
创建列表:首先创建一个
ArrayList
来存储链表中所有节点的值。这一步是为了方便后续进行比较。遍历链表:通过
while
循环遍历链表,将每个节点的值依次添加到list
中,直到链表的末尾(即head
变为null
)。双指针比较:
使用两个指针,
i
从列表的开头开始,j
从列表的末尾开始。在一个
for
循环中,同时向中间移动这两个指针。每次比较
list.get(i)
和list.get(j)
的值。如果发现有不相等的值,则返回
false
,表示链表不是一个回文。返回结果:如果所有对应位置的值都相等,那么链表就是回文,函数返回
true
。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
// 方法:检查链表是否为回文
public boolean isPalindrome(ListNode head) {
// 创建一个动态数组,用于存储链表的值
List<Integer> list = new ArrayList<>();
// 遍历链表,将节点的值存入数组
while (head != null) {
list.add(head.val); // 将当前节点的值添加到列表
head = head.next; // 移动到下一个节点
}
// 使用双指针方法检查列表是否为回文
for (int i = 0, j = list.size() - 1; i <= j; i++, j--) {
// 如果对应位置的值不相等,返回 false
if (!list.get(i).equals(list.get(j))) {
return false; // 当前值不相等,说明不是回文
}
}
// 如果所有值都匹配,返回 true
return true; // 链表是回文
}
}
第二种思路:
这种递归的思路核心点是递归在处理节点的顺序是相反的。
理解递归
基本情况:这个递归函数持续执行,直到
currentNode
为null
。这表示它已经到达了链表的末尾。当currentNode
为null
时,函数不会执行if
语句的主体,而是直接返回true
。递归调用:当调用
recursivelyCheck(currentNode.next)
时,实际上是移步到下一个节点。这个过程会继续递归,直到到达链表的最后一个节点。这时,currentNode
将会是最后一个节点,之后再调用currentNode.next
就会返回null
。逻辑分析
当链表的最后一个节点被访问时,
currentNode
仍然是一个有效的节点。在执行recursivelyCheck
时,它会递归访问链表的所有节点,并最终在到达末尾时返回true
,然后逐层向上返回。每层递归都会检查当前节点的值与
frontPointer
的值,如果不相等则返回false
。只有在完成所有递归调用并且检查都通过的情况下,才会返回true
。
class Solution {
// 用于指向链表的前半部分的指针
private ListNode frontPointer;
// 递归检查链表是否为回文
private boolean recursivelyCheck(ListNode currentNode) {
// 如果当前节点不为空
if (currentNode != null) {
// 递归检查下一个节点
if (!recursivelyCheck(currentNode.next)) {
return false; // 如果下一个节点不满足条件,返回 false
}
// 比较当前节点的值与前指针指向的值
if (currentNode.val != frontPointer.val) {
return false; // 如果不相等,返回 false
}
// 移动前指针到下一个节点
frontPointer = frontPointer.next;
}
// 如果所有节点都匹配,返回 true
return true;
}
// 主方法,检查链表是否为回文
public boolean isPalindrome(ListNode head) {
// 初始化前指针为链表的头节点
frontPointer = head;
// 开始递归检查链表
return recursivelyCheck(head);
}
}