Golang面试考题记录 ━━ 字符串中的第一个唯一字符 ,拓展:ASCII和strings字符串查找的用法

===问:

给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

案例:

s = “leetcode”
返回 0.

s = “loveleetcode”
返回 2.

注意事项:您可以假定该字符串只包含小写字母。

===答:

方法一:
执行用时 :12 ms/16 ms, 击败了66.08%/62.57% 的用户
内存消耗 :5.8 MB, 击败了14.63%的用户

嵌套循环,依次遍历,在几个方法中并不是速度最慢的。

func firstUniqChar1(s string) int {
	l := len(s) - 1

	for i := 0; i <= l; i++ {

		r := 1

		for j := 0; j <= l; j++ {
			if i != j && s[i] == s[j] {
				r = 0
				break
			}
		}

		if r == 1 {
			return i
		}
	}

	return -1
}

方法二:
执行用时 :144 ms, 击败了5.19% 的用户
内存消耗 :5.8 MB, 击败了14.63%的用户

利用go的方法strings.Count(),遍历一次,计算每个字母出现的个数,
遍历过程中一旦某个字母只有唯一一个,立刻停止循环返回其索引号

func firstUniqChar2(s string) int {
	for i := range s {
		c := strings.Count(s, string(s[i]))
		if c == 1 {
			return i
		}
	}
	return -1
}

方法三:
执行用时 :20 ms, 击败了60.58% 的用户
内存消耗 :5.7 MB, 击败了95.12%的用户

利用go的方法strings.LastIndexByte(),遍历当前元素与字符串中最后一个相同元素的索引号是否一致
如果相同,且是第一次检测,则说明是唯一元素,返回索引号
本方法需创建一个map,却消耗内存最低

func firstUniqChar3(s string) int {
	v := make(map[int]int, 26)
	for i := range s {
		c := strings.LastIndexByte(s, s[i])
		if c == i && v[c] != 1 {
			return i
		}
		v[c] = 1
	}
	return -1
}

方法四:
执行用时 :40 ms/36 ms, 击败了46.73%/54.62% 的用户
内存消耗 :5.7 MB, 击败了95.12%的用户

第一次遍历字符串中每个元素,用map记录元素出现的次数,没有使用go自带的strings.Count()方法
第二次遍历字符串每个元素,当发现该元素在map中的值为1则意味着该元素只出现一次,立刻返回索引号
本方法与方法二思路相同,但内存占用和执行效率提升N倍
本方法统计了所有的元素的出现次数,而方法三只获得最后一个元素的位置,因此本方法执行效率慢了一倍。

func firstUniqChar4(s string) int {
	r := make(map[rune]int, 26)

	for _, v := range s {
		r[v]++
	}

	for i, v := range s {
		if r[v] == 1 {
			return i
		}
	}
	return -1
}

方法五:
执行用时 :8 ms,击败了88.08% 的用户
内存消耗 :5.8 MB, 击败了14.63%的用户

取巧的方法,因为题目里说了都是小写字母,否则数组r的长度无法确定
又因为都是小写字母,因此v-97可得到0~26,正好用于数组索引
第一次遍历字符串计算元素出现次数
第二次遍历字符串遇到出现一次的元素返回其索引
方法二、方法四、方法五思路一致,都是统计元素出现次数,但方法五执行效率最高,方法四内存占用最少
推荐方法四,可以用于非小写字母的字符串统计,而方法五适用面太小

func firstUniqChar5(s string) int {
	// ASCII数值:a-z:97-122,A-Z:65-90,0-9:48-57。
	r := [26]int{}

	for _, v := range s {
		r[v-97]++
	}

	for i, v := range s {
		if r[v-97] == 1 {
			return i
		}
	}
	return -1
}

方法六:
执行用时 :12 ms, 击败了66.15% 的用户
内存消耗 :6 MB, 击败了7.32%的用户

本方法是将方法三中的map改为了切片,但效果不明显,执行效率增加,但是内存占用也增加了~~
这是因为当s为类似"aabbccddefggadfdaefdfaefaggjfghtrgregtrujuoiolokuhtrgrfe",在创建切片的时候就会创建一个同样长度的切片,否则如果使用append(),每次更改内存地址,相比速度会减慢,没有必要了。

func firstUniqChar6(s string) int {
	v := make([]int, len(s))
	for i := range s {
		c := strings.LastIndexByte(s, s[i])
		if c == i && v[c] != 1 {
			return i
		}
		v[c] = 1
	}
	return -1
}

方法七:
执行用时 :12 ms, 击败了66.15% 的用户
内存消耗 :5.8 MB, 击败了14.63%的用户

想让方法六中的切片固定为一个数组,看看会不会有所提升于是创建了方法七
果然内存消耗是降低了,但执行效率并没什么改变
方法三中的map可变长度又能自定义键名,反倒省了很多事

func firstUniqChar7(s string) int {
	r := [26]int{}
	for i, v := range s {
		c := strings.LastIndexByte(s, s[i])
		if c == i && r[v-97] != 1 {
			return i
		}
		r[v-97] = 1
	}
	return -1
}

===拓展:

这里需要拓展的知识点有两个:

字母和数字的ASCII值:

ASCII表格我们常用的记住下面的就行了,实际使用的时候结合实际情况,发挥创意:

0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等;通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;ASCII值为8、9、10

和13 分别转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响 [1] 。

32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字。

65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。

go自带的strings统计及字符位置的几个函数

参考:Go语言中字符串的查找方法小结

  1. func Contains(s, substr string) bool这个函数是查找某个字符是否在这个字符串中存在,存在返回true
import (
 "fmt"
 "strings"
)

func main() {
 fmt.Println(strings.Contains("widuu", "wi")) //true
 fmt.Println(strings.Contains("wi", "widuu")) //false
}
  1. func ContainsAny(s, chars string) bool这个是查询字符串中是否包含多个字符
import (
 "fmt"
 "strings"
)

func main() {
 fmt.Println(strings.ContainsAny("widuu", "w&d")) //true
}
  1. func ContainsRune(s string, r rune) bool,这里边当然是字符串中是否包含rune类型,其中rune类型是utf8.RUneCountString可以完整表示全部Unicode字符的类型
import (
 "fmt"
 "strings"
)

func main() {
 fmt.Println(strings.ContainsRune("widuu", rune('w'))) //true
 fmt.Println(strings.ContainsRune("widuu", 20))        //fasle
}
  1. func Count(s, sep string) int这个的作用就是输出,在一段字符串中有多少匹配到的字符
import (
 "fmt"
 "strings"
)

func main() {
 fmt.Println(strings.Count("widuu", "uu")) //1
 fmt.Println(strings.Count("widuu", "u"))  //2
}
  1. func Index(s, sep string) int 这个函数是查找字符串,然后返回当前的位置,输入的都是string类型,然后int的位置信息
import (
 "fmt"
 "strings"
)

func main() {
 fmt.Println(strings.Index("widuu", "i")) //1
 fmt.Println(strings.Index("widuu", "u")) //3
}
  1. func IndexAny(s, chars string) int 这个函数是一样的查找,字符串第一次出现的位置,如果不存在就返回-1
import (
 "fmt"
 "strings"
)

func main() {
 fmt.Println(strings.IndexAny("widuu", "u")) //3
}
  1. func IndexByte(s string, c byte) int,这个函数功能还是查找第一次粗线的位置,只不过这次C是byte类型的,查找到返回位置,找不到返回-1
import (
 "fmt"
 "strings"
)

func main() {
 fmt.Println(strings.IndexByte("hello xiaowei", 'x')) //6
}
  1. func IndexRune(s string, r rune) int,还是查找位置,只不过这次是rune类型的
import (
 "fmt"
 "strings"
)

func main() {
 fmt.Println(strings.IndexRune("widuu", rune('w'))) //0
}
  1. func IndexFunc(s string, f func(rune) bool) int这个函数大家一看就知道了,是通过类型的转换来用函数查找位置,我们来代码看下哈
import (
 "fmt"
 "strings"
)

func main() {
 fmt.Println(strings.IndexFunc("nihaoma", split)) //3
}

func split(r rune) bool {
 if r == 'a' {
  return true
 }
 return false
}
  1. func LastIndex(s, sep string) int 看到这个大家可能也明白了查找的是最后出现的位置,正好跟index相反
import (
 "fmt"
 "strings"
)

func main() {
 fmt.Println(strings.LastIndex("widuu", "u")) // 4
}
  1. func LastIndexAny(s, chars string) int这个跟indexAny正好相反,也是查找最后一个
import (
 "fmt"
 "strings"
)

func main() {
 fmt.Println(strings.LastIndexAny("widuu", "u")) // 4
}
发布了44 篇原创文章 · 获赞 1 · 访问量 3595

猜你喜欢

转载自blog.csdn.net/snans/article/details/105391651