一、移除链表中指定的节点
题目:
/** * 删除链表中等于给定值 val 的所有节点。 * * 示例: * 输入: 1->2->6->3->4->5->6, val = 6 * 输出: 1->2->3->4->5 */
题的描述很简单,做起来并不容易,有一些小坑,要注意的就是头尾的节点的处理和连续相等的情况处理,其它的就非常简单了,遍历链表,将与val相等的前一个节点和下一节点相连。
处理这样的问题肯定会用到while循环,这样更方便也更不容易出错。
因为要处理头结点就是目标节点的情况,所以你可以在开头就加上一个while循环来过滤
在循环中你可以检测下一个节点的val是否等于目标值,如果相等就while判断下一个节点的val是不是目标,直到找到不是的那一个,连接之前的节点,为此你要定义一个局部变量方便操作
在其中你还要注意加上判断以防止尾节点的问题
public static ListNode method1(ListNode head, int val) {
while (head!=null&&head.val==val){
head=head.next;
}
if (head==null){
return null;
}
ListNode operation=head;
while (operation.next!=null){
if (operation.next.val==val){
if (operation.next.next==null){
operation.next=null;
}else {
ListNode transientNode=operation.next;
while (transientNode!=null&&transientNode.val==val){
transientNode=transientNode.next;
}
if (transientNode==null){
operation.next=null;
}else {
operation.next=transientNode;
operation=transientNode;
}
}
}else {
operation=operation.next;
}
}
return head;
}
这样的代码很长很繁琐,还有一堆判断,我们是否可以有一个更好的办法来优化算法
你要考虑头结点的问题,这个问题你可以自己给它加上一个头结点,这样就不用再为头结点的处理加一个循环了
你要考虑连续结点值都是目标值的问题,你可以采用这样的策略,如果下一个节点是目标值,那么就连接next.next,否则向前移动。这样的话即使是连续目标值也能得到判断
public static ListNode method2(ListNode head,int val){
ListNode header = new ListNode(-1);
header.next = head;
ListNode cur = header;
while(cur.next != null){
if(cur.next.val == val ){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
return header.next;
}
这样一看代码是不是就减少了很多了,思路也很清晰,几乎所有的while循环的问题我们都可以用递归解决
我们要关注的问题就是当前节点的val值是不是目标值,如果是就返回next,不是就返回本身
public static ListNode method3(ListNode head,int val){
if(head == null) return null;
head.next = method3(head.next, val);
return head.val == val ? head.next : head;
}
public static class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
是不是思路更为清晰了呢?
二、质数的数量
题目:
/** * 统计所有小于非负整数 n 的质数的数量。 * * 示例: * 输入: 10 * 输出: 4 * 解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。 */
入门的时候肯定学过如何判断一个数是不是质数,也学过一些优化,那就一个一个判断嘛,所以就写下了这段代码
public static int method1(int n) {
int count=0,j=0,k=0;
for (int i=2;i<=n;i++){
k=(int)sqrt(i+1);
for (j=2;j<=k;j++){
if (i%j==0){
break;
}
}
if (j==k+1){
count++;
}
}
return count;
}
然而事情并没有那么简单,它是有时间限制的,给出了一个5000000的大数,我就懵了,仔细研究,一定有什么诀窍
写下20个数,仔细观察,你会发现质数的倍数都不是质数(很傻缺的结论),但是你就可以用这个结论来届这道题,如果你检测到3是一个质数,那么之后的6,9,12,15,18.。。。。就都不是质数了,都不用判断了,这就节省了非常多的判断。
public static int method2(int n){
boolean[] isPrime = new boolean[n];
for (int i = 2; i < n; i++) {
isPrime[i] = true;
}
for (int i = 2; i * i < n; i++) {
if (!isPrime[i]) continue;
for (int j = i * i; j < n; j += i) {
isPrime[j] = false;
}
}
int count = 0;
for (int i = 2; i < n; i++) {
if (isPrime[i]) count++;
}
return count;
}
三、同构字符串
题目:
/** * 给定两个字符串 s 和 t,判断它们是否是同构的。 * 如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。 * 所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。 * * 示例 1: * 输入: s = "egg", t = "add" * 输出: true * * 示例 2: * 输入: s = "foo", t = "bar" * 输出: false * * 示例 3: * 输入: s = "paper", t = "title" * 输出: true */
满足同构字符串有一个关键的条件,那就是字符与字符之间必须是一对一的关系,不能一对多也不能多对一。那我们就使用HashMap建立这样的一对一的映射关系,最后遍历字符串看看是否对应正确。
public static boolean method1(String s, String t) {
if (s.length()!=t.length())return false;
HashMap<Character,Character> hashMap=new HashMap<>();
char[] chars=s.toCharArray();
char[] chart=t.toCharArray();
for (int i=0;i<chars.length;i++){
if (!hashMap.keySet().contains(chars[i])&&hashMap.values().contains(chart[i])){
return false;
}
hashMap.put(chars[i],chart[i]);
}
for (int i=0;i<chars.length;i++){
if (!hashMap.get(chars[i]).equals(chart[i])){
return false;
}
}
return true;
}
这样要遍历字符串两遍,可以优化一下,使它遍历一遍。要完成的任务为一对一的字符映射,借助HashMap,可以先检测键中是否含有该字符,含有的话取出来看看映射的是否正确,没有的话看看值中是否含有对应字符,含有就说明对应关系错误。这样一遍遍历就搞定了。
public static boolean method2(String s, String t) {
if (s.length()!=t.length())return false;
HashMap<Character,Character> hashMap=new HashMap<>();
char[] chars=s.toCharArray();
char[] chart=t.toCharArray();
for (int i=0;i<chars.length;i++){
if (hashMap.keySet().contains(chars[i])){
if (!hashMap.get(chars[i]).equals(chart[i])){
return false;
}
}else {
if (hashMap.values().contains(chart[i])){
return false;
}else {
hashMap.put(chars[i],chart[i]);
}
}
}
return true;
}
还能更为优化一下,我们是借助HashMap来完成字符的一对一映射的,HashMap的底层是使用数组实现的,而字符刚好可以转换为Ascll码当做数组下标来使用,核心思想还是一样,但这样性能上就更为提升了一截。
public static boolean method4(String s,String t){
char[] sc = s.toCharArray();
char[] tc = t.toCharArray();
char[] map = new char[256];
for (int i = sc.length-1;i >= 0;i--) {
if (map[sc[i]] != map[tc[i]+128]) {
return false;
}
map[sc[i]] = map[tc[i] + 128] = sc[i];
}
return true;
}
更换一种思路,两个字符串相互判断,对两个结果使用与连接。不在内部处理一对一,而是简单的检查多对一,相互判断之后的结果也是一对一。
public static boolean method3(String s,String t){
return isIsomorphic(s,t) && isIsomorphic(t,s);
}
private static boolean isIsomorphic(String t, String s) {
char[] dict = new char[256];
Arrays.fill(dict, (char) 0);
for (int i = 0; i < s.length(); i++) {
if (dict[s.charAt(i)] == (char) 0) {
dict[s.charAt(i)] = t.charAt(i);
} else if (dict[s.charAt(i)] != t.charAt(i)) {
return false;
}
}
return true;
}
四、重复元素
题目:
/** * 给定一个整数数组,判断是否存在重复元素。 * 如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。 * * 示例 1: * 输入: [1,2,3,1] * 输出: true * * 示例 2: * 输入: [1,2,3,4] * 输出: false * * 示例 3: * 输入: [1,1,1,3,3,4,3,2,4,2] * 输出: true */
这道题可以说是相当简单了,可以直接使用我们熟知的数据结构Set来进行结题
public static boolean method1(int[] nums) {
HashSet<Integer> integerHashSet=new HashSet<>();
for (int a:nums){
integerHashSet.add(a);
}
if (integerHashSet.size()!=nums.length){
return true;
}else {
return false;
}
}
但我们还是要用一些花样来解这道题,学习嘛,使用Arrays的排序方法对数组排序,这样相同的元素就被放到一起了,然后for循环~
public static boolean method2(int[] nums){
Arrays.sort(nums);
for (int i=0;i<nums.length-1;i++){
if (nums[i]==nums[i+1]){
return true;
}
}
return false;
}
学过java8的同学可能有一些新思路,使用流处理,分组,计数,看看是不是和数组长度相等。使用流不关注实现只关注结果,这样一行代码就搞定了,但实际上他的效率并不高。
public static boolean method3(int[] nums){
return Arrays.stream(nums).distinct().count() != nums.length;
}